<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue 流程图编辑器</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            background-color: #f5f5f5;
            overflow: hidden;
        }

        .toolbar {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            height: 60px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            display: flex;
            align-items: center;
            padding: 0 20px;
            z-index: 1000;
        }

        .toolbar h1 {
            color: white;
            font-size: 20px;
            margin-right: 30px;
        }

        .tool-group {
            display: flex;
            gap: 10px;
        }

        .tool-btn {
            padding: 8px 16px;
            background: rgba(255,255,255,0.2);
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: all 0.3s ease;
            font-size: 14px;
            font-weight: normal;
        }

        .tool-btn:hover {
            background: rgba(255,255,255,0.3);
            transform: translateY(-1px);
        }

        .tool-btn.active {
            background: rgba(255,255,255,0.5);
            font-weight: bold;
        }

        .toolbar-color-picker {
            width: 30px;
            height: 30px;
            border: 2px solid rgba(255,255,255,0.5);
            border-radius: 5px;
            cursor: pointer;
            background: none;
            margin: 0 5px;
        }

        .toolbar-color-picker::-webkit-color-swatch-wrapper {
            padding: 2px;
        }

        .toolbar-color-swatch::-webkit-color-swatch {
            border: none;
            border-radius: 3px;
        }

        .left-panel {
            position: fixed;
            left: 0;
            top: 60px;
            width: 250px;
            height: calc(100vh - 60px);
            background: white;
            box-shadow: 2px 0 10px rgba(0,0,0,0.1);
            z-index: 100;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
        }

        .left-panel-section {
            padding: 20px;
            border-bottom: 1px solid #eee;
        }

        .left-panel-section:last-child {
            border-bottom: none;
        }

        .shape-category h3, .left-panel-section h3 {
            color: #333;
            margin-bottom: 15px;
            font-size: 14px;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        .shape-item {
            width: 60px;
            height: 40px;
            margin: 5px;
            border: 2px solid #ddd;
            border-radius: 5px;
            cursor: grab;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background: #f9f9f9;
            transition: all 0.3s ease;
            font-size: 12px;
            color: white;
            user-select: none;
        }

        .shape-item:active {
            cursor: grabbing;
        }

        .shape-item:hover {
            border-color: #667eea;
            background: #e8f0fe;
            transform: scale(1.05);
        }

        .shape-item.start {
            border-radius: 50%;
            background: #4CAF50;
        }

        .shape-item.process {
            background: #2196F3;
        }

        .shape-item.decision {
            background: #FF9800;
            width: 70px;
            height: 45px;
            clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
            border: none;
        }

        .shape-item.end {
            border-radius: 50%;
            background: #F44336;
        }

        .canvas-container {
            position: absolute;
            left: 250px;
            top: 60px;
            right: 0;
            bottom: 0;
            background: #fff;
            overflow: auto;
            background-image: radial-gradient(circle, #ddd 1px, transparent 1px);
            background-size: 20px 20px;
        }

        .canvas {
            position: relative;
            cursor: grab;
            transform-origin: 0 0;
        }

        .canvas.selecting {
            cursor: crosshair;
        }

        .canvas.panning {
            cursor: grabbing;
        }

        .selection-rectangle {
            position: absolute;
            border: 2px dashed #667eea;
            background: rgba(102, 126, 234, 0.1);
            pointer-events: none;
            z-index: 1000;
        }

        .flowchart-node {
            position: absolute;
            background: white;
            border: 2px solid #333;
            border-radius: 5px;
            padding: 10px;
            min-width: 80px;
            min-height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: move;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            transition: box-shadow 0.3s ease;
            z-index: 10;
            will-change: transform;
            user-select: none;
            -webkit-user-drag: none;
        }

        .flowchart-node:hover {
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
        }

        .flowchart-node.selected {
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
        }

        .flowchart-node.dragging {
            opacity: 0.8;
            cursor: grabbing;
        }

        .flowchart-node.start {
            border-radius: 50%;
            width: 120px;
            height: 80px;
            background: #4CAF50;
            color: white;
        }

        .flowchart-node.end {
            border-radius: 50%;
            width: 100px;
            height: 100px;
        }

        .flowchart-node.end {
            background: #F44336;
            color: white;
        }

        /* 连接线文本标签 */
        .connection-label {
            position: absolute;
            background: white;
            border: 1px solid #667eea;
            border-radius: 4px;
            padding: 4px 8px;
            font-size: 12px;
            color: #333;
            pointer-events: auto;
            cursor: pointer;
            z-index: 15;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            white-space: nowrap;
            transition: all 0.2s ease;
        }

        .connection-label:hover {
            border-color: #4CAF50;
            background: #f9f9f9;
        }

        .connection-label-input {
            border: none;
            outline: none;
            background: transparent;
            font-size: 12px;
            color: #333;
            padding: 0;
            min-width: 40px;
        }

        .connection-label.editing {
            border-color: #4CAF50;
            box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
        }

        .flowchart-node.process {
            background: #2196F3;
            color: white;
        }

        .flowchart-node.decision {
            background: #FF9800;
            color: white;
            width: 120px;
            height: 80px;
            clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
            border: none;
        }

        .node-text {
            font-size: 12px;
            text-align: center;
            outline: none;
            background: transparent;
            border: none;
            color: inherit;
            width: 100%;
            resize: none;
            overflow: hidden;
            font-family: inherit;
            line-height: 1.4;
        }

        /* 文本格式工具栏 */
        .text-format-toolbar {
            position: absolute;
            top: -35px;
            left: 50%;
            transform: translateX(-50%);
            background: white;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 4px;
            display: flex;
            gap: 4px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            z-index: 100;
        }

        .format-btn {
            width: 24px;
            height: 24px;
            border: 1px solid #ddd;
            background: white;
            border-radius: 3px;
            cursor: pointer;
            font-weight: bold;
            font-size: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
        }

        .format-btn:hover {
            background: #f0f0f0;
            border-color: #667eea;
        }

        .format-btn.active {
            background: #667eea;
            color: white;
            border-color: #667eea;
        }

        .format-btn.color-picker {
            width: 24px;
            height: 24px;
            padding: 0;
            border: 1px solid #ddd;
            border-radius: 3px;
            cursor: pointer;
        }

        .format-btn.color-picker::-webkit-color-swatch-wrapper {
            padding: 0;
        }

        .format-btn.color-picker::-webkit-color-swatch {
            border: none;
            border-radius: 2px;
        }

        .delete-btn {
            position: absolute;
            top: -8px;
            right: -8px;
            width: 20px;
            height: 20px;
            background: #F44336;
            color: white;
            border: none;
            border-radius: 50%;
            font-size: 12px;
            cursor: pointer;
            z-index: 20;
            display: none;
        }

        .flowchart-node.selected .delete-btn {
            display: block;
        }

        .connection-point {
            position: absolute;
            width: 16px;
            height: 16px;
            background: #667eea;
            border: 2px solid #fff;
            border-radius: 50%;
            cursor: crosshair;
            z-index: 15;
            opacity: 0;
            transition: all 0.3s ease;
            pointer-events: auto;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }

        .connection-point:hover {
            background: #ff4444;
            transform: scale(1.3);
        }

        .flowchart-node:hover .connection-point {
            opacity: 1;
        }

        .connections-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 15;
        }

        .connection-line {
            stroke: #333;
            stroke-width: 2;
            fill: none;
            pointer-events: stroke;
            cursor: pointer;
            transition: stroke 0.2s ease;
        }

        .connection-line.bidirectional {
            stroke: #FF9800;
            stroke-width: 2.5;
        }

        .connection-line:hover {
            stroke: #667eea;
            stroke-width: 3;
        }

        .connection-line.selected {
            stroke: #667eea;
            stroke-width: 3;
        }

        .connection-line.bidirectional.selected {
            stroke: #667eea;
            stroke-width: 3;
        }

        .zoom-controls {
            position: fixed;
            bottom: 20px;
            right: 20px;
            display: flex;
            flex-direction: column;
            gap: 5px;
            z-index: 100;
        }

        .zoom-btn {
            width: 40px;
            height: 40px;
            background: white;
            border: 1px solid #ddd;
            border-radius: 50%;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 18px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }

        .zoom-btn:hover {
            background: #f0f0f0;
        }

        .info-panel {
            position: fixed;
            bottom: 20px;
            left: 270px;
            background: rgba(255,255,255,0.95);
            padding: 10px 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            font-size: 12px;
            color: #666;
            z-index: 100;
        }

        .info-item {
            margin: 2px 0;
        }

        /* 隐藏连接线的类 */
        .connections-hidden {
            display: none;
        }

        /* 右键菜单 */
        .context-menu {
            position: fixed;
            background: white;
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            padding: 5px 0;
            min-width: 150px;
            z-index: 2100;
        }

        .context-menu-item {
            padding: 10px 20px;
            cursor: pointer;
            font-size: 14px;
            color: #333;
            transition: background 0.2s;
            user-select: none;
        }

        .context-menu-item:hover {
            background: #f0f0f0;
        }

        .context-menu-item.disabled {
            color: #ccc;
            cursor: not-allowed;
        }

        .context-menu-item.disabled:hover {
            background: transparent;
        }

        /* 有子流程的节点 */
        .flowchart-node.process.has-subprocess {
            background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
            border: 2px solid #1565C0;
            box-shadow: 0 3px 10px rgba(33, 150, 243, 0.3);
        }

        /* 子流程指示器 */
        .subprocess-indicator {
            position: absolute;
            bottom: 5px;
            right: 5px;
            width: 20px;
            height: 20px;
            background: rgba(255, 255, 255, 0.9);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            font-weight: bold;
            color: #1976D2;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }

        /* 子流程视图 */
        .subprocess-view {
            position: fixed;
            left: 250px;
            top: 60px;
            right: 0;
            bottom: 0;
            background: white;
            z-index: 2000;
            display: flex;
            flex-direction: column;
        }

        .subprocess-header {
            padding: 15px 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            flex-shrink: 0;
        }

        .subprocess-title {
            font-size: 18px;
            font-weight: bold;
        }

        .subprocess-close {
            background: rgba(255, 255, 255, 0.2);
            border: none;
            color: white;
            padding: 8px 16px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            transition: background 0.3s;
        }

        .subprocess-close:hover {
            background: rgba(255, 255, 255, 0.3);
        }

        .subprocess-body {
            flex: 1;
            position: relative;
            overflow: hidden;
        }

        .subprocess-body .canvas-container {
            position: absolute;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
            overflow: auto;
            background: #fff;
            background-image: radial-gradient(circle, #ddd 1px, transparent 1px);
            background-size: 20px 20px;
        }

        /* 目录面板 */
        .directory-panel {
            position: fixed;
            right: 0;
            top: 60px;
            width: 300px;
            height: calc(100vh - 60px);
            background: white;
            box-shadow: -2px 0 10px rgba(0,0,0,0.1);
            z-index: 99;
            display: flex;
            flex-direction: column;
            transition: transform 0.3s ease;
        }

        .directory-panel.hidden {
            transform: translateX(100%);
        }

        .directory-header {
            padding: 15px 20px;
            background: #f5f5f5;
            border-bottom: 1px solid #ddd;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .directory-title {
            font-size: 16px;
            font-weight: bold;
            color: #333;
        }

        .directory-toggle {
            position: absolute;
            left: -40px;
            top: 50%;
            transform: translateY(-50%);
            background: white;
            border: 1px solid #ddd;
            border-right: none;
            border-radius: 5px 0 0 5px;
            width: 40px;
            height: 60px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 18px;
            box-shadow: -2px 0 5px rgba(0,0,0,0.1);
        }

        .directory-list {
            flex: 1;
            overflow-y: auto;
            padding: 10px;
        }

        .directory-item {
            padding: 12px 15px;
            margin-bottom: 8px;
            background: #f9f9f9;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.3s;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .directory-item:hover {
            background: #e8f0fe;
            transform: translateX(-3px);
        }

        .directory-item.active {
            background: #667eea;
            color: white;
        }

        .directory-item-name {
            flex: 1;
            font-size: 14px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .directory-item-time {
            font-size: 11px;
            color: #999;
            margin-left: 10px;
        }

        .directory-item.active .directory-item-time {
            color: rgba(255, 255, 255, 0.8);
        }

        .directory-item-delete {
            background: #f44336;
            border: none;
            color: white;
            width: 24px;
            height: 24px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 14px;
            margin-left: 8px;
            display: none;
        }

        .directory-item:hover .directory-item-delete {
            display: block;
        }

        .directory-new-btn {
            margin: 10px;
            padding: 12px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
            transition: background 0.3s;
        }

        .directory-new-btn:hover {
            background: #45a049;
        }

        .auto-save-indicator {
            position: fixed;
            bottom: 80px;
            left: 270px;
            background: rgba(76, 175, 80, 0.9);
            color: white;
            padding: 8px 12px;
            border-radius: 20px;
            font-size: 12px;
            z-index: 2100;
            opacity: 0;
            transition: opacity 0.3s;
        }

        .auto-save-indicator.show {
            opacity: 1;
        }

        .node-list-content {
            max-height: 200px;
            overflow-y: auto;
            margin-top: 10px;
        }

        .node-list-item {
            padding: 10px 12px;
            margin-bottom: 6px;
            background: #f9f9f9;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            gap: 10px;
            border-left: 3px solid transparent;
        }

        .node-list-item:hover {
            background: #e8f0fe;
            transform: translateX(-3px);
        }

        .node-list-item.active {
            background: #667eea;
            color: white;
            border-left-color: #4CAF50;
        }

        .node-type-badge {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            flex-shrink: 0;
        }

        .node-type-badge.start {
            background: #4CAF50;
            color: white;
        }

        .node-type-badge.process {
            background: #2196F3;
            color: white;
        }

        .node-type-badge.decision {
            background: #FF9800;
            color: white;
        }

        .node-type-badge.end {
            background: #F44336;
            color: white;
        }

        .node-list-item-text {
            flex: 1;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            font-size: 13px;
        }

        .node-list-subprocess-indicator {
            width: 20px;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 14px;
            color: #667eea;
            font-weight: bold;
            flex-shrink: 0;
        }

        .node-list-item.active .node-list-subprocess-indicator {
            color: white;
        }

        .minimap-content {
            position: relative;
            height: 200px;
            overflow: hidden;
            background: #f9f9f9;
            cursor: pointer;
            border: 1px solid #ddd;
            border-radius: 5px;
            margin-top: 10px;
        }

        .minimap-canvas {
            width: 100%;
            height: 100%;
            border: none;
        }

        .minimap-viewport {
            position: absolute;
            border: 2px solid #667eea;
            background: rgba(102, 126, 234, 0.1);
            pointer-events: none;
            z-index: 10;
        }

        .minimap-node {
            position: absolute;
            border: 1px solid #333;
            cursor: pointer;
        }

        .minimap-node.start {
            border-radius: 50%;
            background: #4CAF50;
        }

        .minimap-node.end {
            border-radius: 50%;
        }

        .minimap-node.process {
            background: #2196F3;
        }

        .minimap-node.decision {
            background: #FF9800;
            clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
        }

        .minimap-node.end {
            background: #F44336;
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- 工具栏 -->
        <div class="toolbar">
            <h1>Vue 流程图编辑器 - 优化版</h1>

            <!-- 背景色工具栏 -->
            <div class="tool-group" v-if="selectedNodes.size > 0" style="margin-right: 20px; border-left: 1px solid rgba(255,255,255,0.3); padding-left: 20px;">
                <label style="color: white; font-size: 12px; margin-right: 5px;">背景色:</label>
                <input
                    type="color"
                    :value="getCurrentBgColor()"
                    @input="applyBackgroundColor($event.target.value)"
                    title="背景颜色"
                    class="toolbar-color-picker"
                />
            </div>

            <!-- 返回按钮 -->
            <div class="tool-group" v-if="currentSubprocess" style="margin-right: 20px; border-left: 1px solid rgba(255,255,255,0.3); padding-left: 20px;">
                <button class="tool-btn" @click="closeSubprocessModal" style="background: rgba(100,150,255,0.3);">⬆️ 返回上一级</button>
                <button class="tool-btn" @click="exitSubprocess" style="background: rgba(255,200,0,0.3);">🏠 返回主流程</button>
            </div>

            <div class="tool-group">
                <button class="tool-btn" @click="toggleDirectory">📁 目录</button>
                <button class="tool-btn" @click="exportFlow">📤 导出当前流程</button>
                <button class="tool-btn" @click="exportAllFlows">📦 导出所有流程</button>
                <button class="tool-btn" @click="importFlow">📥 导入流程</button>
                <button class="tool-btn" @click="clearAll">清空画布</button>
            </div>
        </div>

        <!-- 左侧面板 -->
        <div class="left-panel">
            <!-- 基础形状 -->
            <div class="left-panel-section">
                <div class="shape-category">
                    <h3>基础形状</h3>
                    <div
                        class="shape-item start"
                        draggable="true"
                        @dragstart="onShapeDragStart($event, 'start')"
                    >
                        开始
                    </div>
                    <div
                        class="shape-item process"
                        draggable="true"
                        @dragstart="onShapeDragStart($event, 'process')"
                    >
                        过程
                    </div>
                    <div
                        class="shape-item decision"
                        draggable="true"
                        @dragstart="onShapeDragStart($event, 'decision')"
                    >
                        判断
                    </div>
                    <div
                        class="shape-item end"
                        draggable="true"
                        @dragstart="onShapeDragStart($event, 'end')"
                    >
                        结束
                    </div>
                </div>
            </div>

            <!-- 节点列表 -->
            <div class="left-panel-section">
                <h3>节点列表</h3>
                <div class="node-list-content">
                    <div
                        v-for="node in nodes"
                        :key="node.id"
                        :class="['node-list-item', { active: selectedNodes.has(node.id) }]"
                        @click="locateNode(node)"
                        @dblclick="onNodeListItemDblClick(node)"
                    >
                        <div :class="['node-type-badge', node.type]">
                            {{ node.type === 'start' ? '始' : node.type === 'end' ? '终' : node.type === 'decision' ? '判' : '程' }}
                        </div>
                        <div class="node-list-item-text">{{ stripHtmlTags(node.text) }}</div>
                        <div v-if="node.type === 'process' && node.subprocessId" class="node-list-subprocess-indicator">➜</div>
                    </div>
                </div>
            </div>

            <!-- 缩略图 -->
            <div class="left-panel-section">
                <h3>缩略图</h3>
                <div class="minimap-content" @click="onMinimapClick" ref="minimapContent">
                    <div
                        v-for="node in nodes"
                        :key="'mini-' + node.id"
                        :class="['minimap-node', node.type]"
                        :style="getMinimapNodeStyle(node)"
                    ></div>
                    <div class="minimap-viewport" :style="minimapViewportStyle"></div>
                </div>
            </div>
        </div>

        <!-- 画布容器 -->
        <div class="canvas-container" ref="canvasContainer">
            <div
                class="canvas"
                :class="{ selecting: isSelecting, panning: isPanning }"
                :style="{
                    transform: `scale(${zoom})`,
                    transformOrigin: '0 0',
                    width: '10000px',
                    height: '10000px'
                }"
                @mousedown="onCanvasMouseDown"
                @mousemove="onCanvasMouseMove"
                @mouseup="onCanvasMouseUp"
                @dragover.prevent
                @drop="onCanvasDrop"
            >
                <!-- 框选矩形 -->
                <div
                    v-if="selectionRect.visible"
                    class="selection-rectangle"
                    :style="{
                        left: selectionRect.left + 'px',
                        top: selectionRect.top + 'px',
                        width: selectionRect.width + 'px',
                        height: selectionRect.height + 'px'
                    }"
                ></div>

                <!-- 节点 -->
                <div
                    v-for="node in nodes"
                    :key="node.id"
                    :data-node-id="node.id"
                    :class="['flowchart-node', node.type, {
                        selected: selectedNodes.has(node.id),
                        dragging: isDragging && selectedNodes.has(node.id),
                        'has-subprocess': node.type === 'process' && node.subprocessId
                    }]"
                    :style="{
                        transform: `translate(${node.x}px, ${node.y}px)`,
                        background: node.backgroundColor || ''
                    }"
                    @mousedown="onNodeMouseDown($event, node)"
                    @contextmenu="onNodeContextMenu($event, node)"
                >
                    <div
                        class="node-text"
                        contenteditable="true"
                        v-html="node.text"
                        @blur="onNodeTextBlur($event, node)"
                        @keydown="handleNodeTextKeydown($event, node)"
                        @input="onNodeTextInput($event, node)"
                        @compositionstart="onCompositionStart($event)"
                        @compositionend="onCompositionEnd($event, node)"
                    ></div>

                    <button class="delete-btn" @click="deleteNode(node.id)">×</button>

                    <!-- 子流程指示器 -->
                    <div v-if="node.type === 'process' && node.subprocessId" class="subprocess-indicator" @click="enterSubprocess(node)">
                        ➜
                    </div>

                    <!-- 连接点 -->
                    <div
                        class="connection-point"
                        style="top: -4px; left: 50%; transform: translateX(-50%);"
                        @mousedown.stop="startConnection($event, node, 'top')"
                    ></div>
                    <div
                        class="connection-point"
                        style="bottom: -4px; left: 50%; transform: translateX(-50%);"
                        @mousedown.stop="startConnection($event, node, 'bottom')"
                    ></div>
                    <div
                        class="connection-point"
                        style="left: -4px; top: 50%; transform: translateY(-50%);"
                        @mousedown.stop="startConnection($event, node, 'left')"
                    ></div>
                    <div
                        class="connection-point"
                        style="right: -4px; top: 50%; transform: translateY(-50%);"
                        @mousedown.stop="startConnection($event, node, 'right')"
                    ></div>
                </div>

                <!-- 连接线层 -->
                <svg
                    class="connections-layer"
                    :class="{ 'connections-hidden': isDragging }"
                >
                    <defs>
                        <marker id="arrowhead" markerWidth="6" markerHeight="6"
                                refX="6" refY="3" orient="auto" markerUnits="strokeWidth">
                            <polygon points="0 0, 6 3, 0 6" fill="#333"/>
                        </marker>
                        <marker id="arrowhead-hover" markerWidth="6" markerHeight="6"
                                refX="6" refY="3" orient="auto" markerUnits="strokeWidth">
                            <polygon points="0 0, 6 3, 0 6" fill="#667eea"/>
                        </marker>
                        <marker id="arrowhead-start" markerWidth="6" markerHeight="6"
                                refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
                            <polygon points="6 0, 0 3, 6 6" fill="#FF9800"/>
                        </marker>
                        <marker id="arrowhead-start-hover" markerWidth="6" markerHeight="6"
                                refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
                            <polygon points="6 0, 0 3, 6 6" fill="#667eea"/>
                        </marker>
                        <marker id="arrowhead-bidirectional" markerWidth="6" markerHeight="6"
                                refX="6" refY="3" orient="auto" markerUnits="strokeWidth">
                            <polygon points="0 0, 6 3, 0 6" fill="#FF9800"/>
                        </marker>
                    </defs>
                    <path
                        v-for="conn in getVisibleConnections()"
                        :key="conn.id"
                        :class="['connection-line', {
                            selected: selectedConnection === conn.id,
                            bidirectional: conn.isBidirectional
                        }]"
                        :d="getConnectionPath(conn)"
                        :marker-end="conn.isBidirectional ? (selectedConnection === conn.id ? 'url(#arrowhead-hover)' : 'url(#arrowhead-bidirectional)') : (selectedConnection === conn.id ? 'url(#arrowhead-hover)' : 'url(#arrowhead)')"
                        :marker-start="conn.isBidirectional ? (selectedConnection === conn.id ? 'url(#arrowhead-start-hover)' : 'url(#arrowhead-start)') : ''"
                        @click="selectConnection(conn.id)"
                        @dblclick="addConnectionLabel($event, conn)"
                    />
                </svg>

                <!-- 连接线文本标签 -->
                <div
                    v-for="conn in connections"
                    :key="'label-' + conn.id"
                    v-show="conn.label || editingLabelId === conn.id"
                    :class="['connection-label', { editing: editingLabelId === conn.id }]"
                    :style="getConnectionLabelStyle(conn)"
                    @mousedown.stop
                    @dblclick="startEditingLabel($event, conn)"
                >
                    <input
                        v-if="editingLabelId === conn.id"
                        ref="labelInput"
                        v-model="conn.label"
                        class="connection-label-input"
                        @blur="stopEditingLabel"
                        @keydown.enter="$event.target.blur()"
                        @mousedown.stop
                        placeholder="输入文本"
                    />
                    <span v-else>{{ conn.label }}</span>
                </div>
            </div>
        </div>

        <!-- 缩放控制 -->
        <div class="zoom-controls">
            <button class="zoom-btn" @click="zoomIn">+</button>
            <button class="zoom-btn" @click="zoomOut">-</button>
            <button class="zoom-btn" @click="fitToView">⌂</button>
        </div>

        <!-- 信息面板 -->
        <div class="info-panel">
            <div class="info-item">节点数: {{ nodes.length }}</div>
            <div class="info-item">连接数: {{ connections.length }}</div>
            <div class="info-item">缩放: {{ Math.round(zoom * 100) }}%</div>
            <div class="info-item" v-if="isDragging" style="color: #4CAF50; font-weight: bold;">
                🚀 拖动中 (连接线已隐藏)
            </div>
            <div class="info-item" v-if="currentSubprocess" style="color: #FF9800; font-weight: bold;">
                📁 子流程: {{ stripHtmlTags(currentSubprocess.parentNode?.text) || '未知' }}
            </div>
            <div class="info-item" v-if="currentFlowName" style="color: #2196F3;">
                📄 {{ currentFlowName }}
            </div>
        </div>

        <!-- 右键菜单 -->
        <div
            v-if="contextMenu.visible"
            class="context-menu"
            :style="{
                left: contextMenu.x + 'px',
                top: contextMenu.y + 'px'
            }"
        >
            <div
                class="context-menu-item"
                :class="{ disabled: contextMenu.node?.subprocessId }"
                @click="createSubprocess"
            >
                {{ contextMenu.node?.subprocessId ? '已有子流程' : '新建子流程' }}
            </div>
            <div
                class="context-menu-item"
                :class="{ disabled: contextMenu.node?.subprocessId }"
                @click="importSubprocess"
            >
                导入子流程
            </div>
            <div
                v-if="contextMenu.node?.subprocessId"
                class="context-menu-item"
                @click="openSubprocessModal(contextMenu.node)"
            >
                进入子流程
            </div>
        </div>

        <!-- 目录面板 -->
        <div class="directory-panel" :class="{ hidden: !showDirectory }">
            <div class="directory-toggle" @click="toggleDirectory">
                {{ showDirectory ? '→' : '←' }}
            </div>
            <div class="directory-header">
                <div class="directory-title">流程目录</div>
            </div>

            <button class="directory-new-btn" @click="createNewFlow">+ 新建流程</button>
            <div class="directory-list">
                <div
                    v-for="flow in flowDirectory"
                    :key="flow.id"
                    :class="['directory-item', { active: flow.id === currentFlowId }]"
                    @click="loadFlow(flow.id)"
                    @contextmenu.prevent="openLocalFolder"
                >
                    <div class="directory-item-name">{{ flow.name }}</div>
                    <div class="directory-item-time">{{ formatTime(flow.updatedAt) }}</div>
                    <button class="directory-item-delete" @click.stop="deleteFlow(flow.id)">×</button>
                </div>
            </div>
        </div>

        <!-- 子流程显示 -->
        <div v-if="subprocessModal.visible" class="subprocess-view">
            <div class="subprocess-header">
                <div class="subprocess-title">
                    子流程: {{ stripHtmlTags(subprocessModal.parentNode?.text) || '未知' }}
                </div>
            </div>
            <div class="subprocess-body">
                    <div class="canvas-container">
                        <div
                            class="canvas"
                            :class="{ selecting: isSelecting, panning: isPanning }"
                            :style="{
                                transform: `scale(${zoom})`,
                                transformOrigin: '0 0',
                                width: '10000px',
                                height: '10000px'
                            }"
                            @mousedown="onCanvasMouseDown"
                            @mousemove="onCanvasMouseMove"
                            @mouseup="onCanvasMouseUp"
                            @dragover.prevent
                            @drop="onCanvasDrop"
                        >
                            <!-- 框选矩形 -->
                            <div
                                v-if="selectionRect.visible"
                                class="selection-rectangle"
                                :style="{
                                    left: selectionRect.left + 'px',
                                    top: selectionRect.top + 'px',
                                    width: selectionRect.width + 'px',
                                    height: selectionRect.height + 'px'
                                }"
                            ></div>

                            <!-- 节点 -->
                            <div
                                v-for="node in nodes"
                                :key="node.id"
                                :data-node-id="node.id"
                                :class="['flowchart-node', node.type, {
                                    selected: selectedNodes.has(node.id),
                                    dragging: isDragging && selectedNodes.has(node.id),
                                    'has-subprocess': node.type === 'process' && node.subprocessId
                                }]"
                                :style="{
                                    transform: `translate(${node.x}px, ${node.y}px)`,
                                    background: node.backgroundColor || ''
                                }"
                                @mousedown="onNodeMouseDown($event, node)"
                                @contextmenu="onNodeContextMenu($event, node)"
                            >
                                <div
                                    class="node-text"
                                    contenteditable="true"
                                    v-html="node.text"
                                    @blur="onNodeTextBlur($event, node)"
                                    @keydown="handleNodeTextKeydown($event, node)"
                                    @input="onNodeTextInput($event, node)"
                                    @compositionstart="onCompositionStart($event)"
                                    @compositionend="onCompositionEnd($event, node)"
                                ></div>

                                <button class="delete-btn" @click="deleteNode(node.id)">×</button>

                                <!-- 子流程指示器 -->
                                <div v-if="node.type === 'process' && node.subprocessId" class="subprocess-indicator" @click="openSubprocessModal(node)">
                                    ➜
                                </div>

                                <!-- 连接点 -->
                                <div
                                    class="connection-point"
                                    style="top: -4px; left: 50%; transform: translateX(-50%);"
                                    @mousedown.stop="startConnection($event, node, 'top')"
                                ></div>
                                <div
                                    class="connection-point"
                                    style="bottom: -4px; left: 50%; transform: translateX(-50%);"
                                    @mousedown.stop="startConnection($event, node, 'bottom')"
                                ></div>
                                <div
                                    class="connection-point"
                                    style="left: -4px; top: 50%; transform: translateY(-50%);"
                                    @mousedown.stop="startConnection($event, node, 'left')"
                                ></div>
                                <div
                                    class="connection-point"
                                    style="right: -4px; top: 50%; transform: translateY(-50%);"
                                    @mousedown.stop="startConnection($event, node, 'right')"
                                ></div>
                            </div>

                            <!-- 连接线层 -->
                            <svg
                                class="connections-layer"
                                :class="{ 'connections-hidden': isDragging }"
                            >
                                <defs>
                                    <marker id="arrowhead-sub" markerWidth="6" markerHeight="6"
                                            refX="6" refY="3" orient="auto" markerUnits="strokeWidth">
                                        <polygon points="0 0, 6 3, 0 6" fill="#333"/>
                                    </marker>
                                    <marker id="arrowhead-hover-sub" markerWidth="6" markerHeight="6"
                                            refX="6" refY="3" orient="auto" markerUnits="strokeWidth">
                                        <polygon points="0 0, 6 3, 0 6" fill="#667eea"/>
                                    </marker>
                                    <marker id="arrowhead-start-sub" markerWidth="6" markerHeight="6"
                                            refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
                                        <polygon points="6 0, 0 3, 6 6" fill="#FF9800"/>
                                    </marker>
                                    <marker id="arrowhead-start-hover-sub" markerWidth="6" markerHeight="6"
                                            refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
                                        <polygon points="6 0, 0 3, 6 6" fill="#667eea"/>
                                    </marker>
                                    <marker id="arrowhead-bidirectional-sub" markerWidth="6" markerHeight="6"
                                            refX="6" refY="3" orient="auto" markerUnits="strokeWidth">
                                        <polygon points="0 0, 6 3, 0 6" fill="#FF9800"/>
                                    </marker>
                                </defs>
                                <path
                                    v-for="conn in getVisibleConnections()"
                                    :key="conn.id"
                                    :class="['connection-line', {
                                        selected: selectedConnection === conn.id,
                                        bidirectional: conn.isBidirectional
                                    }]"
                                    :d="getConnectionPath(conn)"
                                    :marker-end="conn.isBidirectional ? (selectedConnection === conn.id ? 'url(#arrowhead-hover-sub)' : 'url(#arrowhead-bidirectional-sub)') : (selectedConnection === conn.id ? 'url(#arrowhead-hover-sub)' : 'url(#arrowhead-sub)')"
                                    :marker-start="conn.isBidirectional ? (selectedConnection === conn.id ? 'url(#arrowhead-start-hover-sub)' : 'url(#arrowhead-start-sub)') : ''"
                                    @click="selectConnection(conn.id)"
                                    @dblclick="addConnectionLabel($event, conn)"
                                />
                            </svg>

                            <!-- 连接线文本标签 -->
                            <div
                                v-for="conn in connections"
                                :key="'label-' + conn.id"
                                v-show="conn.label || editingLabelId === conn.id"
                                :class="['connection-label', { editing: editingLabelId === conn.id }]"
                                :style="getConnectionLabelStyle(conn)"
                                @mousedown.stop
                                @dblclick="startEditingLabel($event, conn)"
                            >
                                <input
                                    v-if="editingLabelId === conn.id"
                                    ref="labelInput"
                                    v-model="conn.label"
                                    class="connection-label-input"
                                    @blur="stopEditingLabel"
                                    @keydown.enter="$event.target.blur()"
                                    @mousedown.stop
                                    placeholder="输入文本"
                                />
                                <span v-else>{{ conn.label }}</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

        <!-- 自动保存提示 -->
        <div class="auto-save-indicator" :class="{ show: showAutoSaveIndicator }">
            ✓ 自动保存成功
        </div>

        <!-- 文件输入 -->
        <input type="file" ref="fileInput" accept=".ywb,.json" style="display: none" @change="handleFileLoad">
        <input type="file" ref="subprocessFileInput" accept=".ywb,.json" style="display: none" @change="handleSubprocessFileLoad">
    </div>

    <script>
        // 反调试和防篡改保护
        (function() {
            'use strict';

            // 1. 检测开发者工具
            const devToolsChecker = function() {
                const threshold = 160;
                const widthThreshold = window.outerWidth - window.innerWidth > threshold;
                const heightThreshold = window.outerHeight - window.innerHeight > threshold;

                if (widthThreshold || heightThreshold) {
                    // 检测到开发者工具打开
                    document.body.innerHTML = '';
                    while(1) {
                        debugger;
                    }
                }
            };

            // 2. 禁用右键和常用快捷键
            document.addEventListener('contextmenu', function(e) {
                e.preventDefault();
                return false;
            });

            document.addEventListener('keydown', function(e) {
                // F12
                if (e.keyCode === 123) {
                    e.preventDefault();
                    return false;
                }
                // Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+Shift+C, Ctrl+U
                if (e.ctrlKey && e.shiftKey && (e.keyCode === 73 || e.keyCode === 74 || e.keyCode === 67)) {
                    e.preventDefault();
                    return false;
                }
                if (e.ctrlKey && e.keyCode === 85) {
                    e.preventDefault();
                    return false;
                }
            });

            // 3. 定时检测开发者工具
            setInterval(devToolsChecker, 1000);

            // 4. 检测调试器
            const detectDebugger = function() {
                const start = new Date();
                debugger;
                const end = new Date();
                if (end - start > 100) {
                    document.body.innerHTML = '';
                    while(1) {}
                }
            };

            setInterval(detectDebugger, 2000);

            // 5. 防止代码格式化
            const antiFormat = Function.prototype.toString;
            Function.prototype.toString = function() {
                if (this === antiFormat) {
                    return 'function toString() { [native code] }';
                }
                return antiFormat.call(this);
            };

            // 6. 代码完整性校验
            const scriptContent = document.scripts[document.scripts.length - 1].textContent;
            const originalHash = 'PLACEHOLDER_HASH'; // 这个会在混淆后替换

            // 7. 禁用控制台
            if (window.console) {
                const noop = function() {};
                const methods = ['log', 'debug', 'info', 'warn', 'error', 'dir', 'trace', 'assert'];
                methods.forEach(function(method) {
                    console[method] = noop;
                });
            }

            // 8. 检测Chrome扩展
            if (window.chrome && window.chrome.runtime && window.chrome.runtime.id) {
                document.body.innerHTML = '';
            }

        })();

        const { createApp, ref, reactive, computed, nextTick, onMounted, onUnmounted, watch } = Vue;

        createApp({
            setup() {
                // 状态定义
                const nodes = ref([]);
                const connections = ref([]);
                const selectedNodes = ref(new Set());
                const selectedConnection = ref(null);
                const isDragging = ref(false);
                const isConnecting = ref(false);
                const isSelecting = ref(false);
                const isPanning = ref(false); // 画布拖动状态
                const zoom = ref(1);
                const fileInput = ref(null);
                const subprocessFileInput = ref(null);
                const canvasContainer = ref(null);
                const editingNodeId = ref(null);
                const nodeInput = ref(null);
                const editingLabelId = ref(null);
                const labelInput = ref(null);
                const panStart = ref({ x: 0, y: 0 }); // 拖动起始位置
                const currentMode = ref('pan'); // 当前模式: 'pan' | 'select'

                // 右键菜单
                const contextMenu = reactive({
                    visible: false,
                    x: 0,
                    y: 0,
                    node: null,
                    pendingNode: null  // 用于保存待导入子流程的节点
                });

                // 子流程管理
                const subprocesses = ref({}); // { subprocessId: { nodes: [], connections: [] } }
                const currentSubprocess = ref(null); // { parentNode, subprocessId }
                const subprocessStack = ref([]); // 子流程调用栈，用于支持嵌套子流程

                // 子流程弹窗
                const subprocessModal = reactive({
                    visible: false,
                    parentNode: null,
                    subprocessId: null
                });

                // 目录管理
                const showDirectory = ref(false);
                const flowDirectory = ref([]);
                const currentFlowId = ref(null);
                const currentFlowName = ref('');
                const showAutoSaveIndicator = ref(false);
                const autoSaveTimer = ref(null);

                // 节点列表和缩略图
                const showNodeList = ref(false);
                const showMinimap = ref(false);
                const minimapContent = ref(null);
                const scrollPosition = ref({ left: 0, top: 0 }); // 用于触发缩略图更新

                // 拖拽相关
                const dragStartPos = ref({ x: 0, y: 0 });
                const dragNodeStartPositions = ref(new Map());

                // 框选相关
                const selectionRect = reactive({
                    visible: false,
                    startX: 0,
                    startY: 0,
                    left: 0,
                    top: 0,
                    width: 0,
                    height: 0
                });

                // 连接相关
                const connectionStart = ref(null);

                // 历史记录
                const history = ref([]);
                const historyIndex = ref(-1);

                // 计数器
                const nodeCounter = ref(0);
                const connectionCounter = ref(0);

                // 建议的保存路径
                const suggestedPath = ref('建议创建 menu 文件夹');

                // 输入法组合状态
                const isComposing = ref(false);

                // 加密密钥（可以修改为更复杂的密钥）
                const ENCRYPTION_KEY = 'FlowchartEditor2025';

                // 加密/解密和校验相关工具函数

                // MD5 哈希函数
                const md5 = (str) => {
                    const utf8 = unescape(encodeURIComponent(str));

                    const rotateLeft = (lValue, iShiftBits) => {
                        return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
                    };

                    const addUnsigned = (lX, lY) => {
                        const lX4 = (lX & 0x40000000);
                        const lY4 = (lY & 0x40000000);
                        const lX8 = (lX & 0x80000000);
                        const lY8 = (lY & 0x80000000);
                        const lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
                        if (lX4 & lY4) {
                            return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
                        }
                        if (lX4 | lY4) {
                            if (lResult & 0x40000000) {
                                return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
                            } else {
                                return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
                            }
                        } else {
                            return (lResult ^ lX8 ^ lY8);
                        }
                    };

                    const F = (x, y, z) => { return (x & y) | ((~x) & z); };
                    const G = (x, y, z) => { return (x & z) | (y & (~z)); };
                    const H = (x, y, z) => { return (x ^ y ^ z); };
                    const I = (x, y, z) => { return (y ^ (x | (~z))); };

                    const FF = (a, b, c, d, x, s, ac) => {
                        a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));
                        return addUnsigned(rotateLeft(a, s), b);
                    };

                    const GG = (a, b, c, d, x, s, ac) => {
                        a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));
                        return addUnsigned(rotateLeft(a, s), b);
                    };

                    const HH = (a, b, c, d, x, s, ac) => {
                        a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));
                        return addUnsigned(rotateLeft(a, s), b);
                    };

                    const II = (a, b, c, d, x, s, ac) => {
                        a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));
                        return addUnsigned(rotateLeft(a, s), b);
                    };

                    const convertToWordArray = (str) => {
                        let lWordCount;
                        const lMessageLength = str.length;
                        const lNumberOfWords_temp1 = lMessageLength + 8;
                        const lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
                        const lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
                        const lWordArray = Array(lNumberOfWords - 1);
                        let lBytePosition = 0;
                        let lByteCount = 0;
                        while (lByteCount < lMessageLength) {
                            lWordCount = (lByteCount - (lByteCount % 4)) / 4;
                            lBytePosition = (lByteCount % 4) * 8;
                            lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition));
                            lByteCount++;
                        }
                        lWordCount = (lByteCount - (lByteCount % 4)) / 4;
                        lBytePosition = (lByteCount % 4) * 8;
                        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
                        lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
                        lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
                        return lWordArray;
                    };

                    const wordToHex = (lValue) => {
                        let WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
                        for (lCount = 0; lCount <= 3; lCount++) {
                            lByte = (lValue >>> (lCount * 8)) & 255;
                            WordToHexValue_temp = "0" + lByte.toString(16);
                            WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
                        }
                        return WordToHexValue;
                    };

                    const x = convertToWordArray(utf8);
                    let a = 0x67452301;
                    let b = 0xEFCDAB89;
                    let c = 0x98BADCFE;
                    let d = 0x10325476;

                    for (let k = 0; k < x.length; k += 16) {
                        const AA = a;
                        const BB = b;
                        const CC = c;
                        const DD = d;
                        a = FF(a, b, c, d, x[k + 0], 7, 0xD76AA478);
                        d = FF(d, a, b, c, x[k + 1], 12, 0xE8C7B756);
                        c = FF(c, d, a, b, x[k + 2], 17, 0x242070DB);
                        b = FF(b, c, d, a, x[k + 3], 22, 0xC1BDCEEE);
                        a = FF(a, b, c, d, x[k + 4], 7, 0xF57C0FAF);
                        d = FF(d, a, b, c, x[k + 5], 12, 0x4787C62A);
                        c = FF(c, d, a, b, x[k + 6], 17, 0xA8304613);
                        b = FF(b, c, d, a, x[k + 7], 22, 0xFD469501);
                        a = FF(a, b, c, d, x[k + 8], 7, 0x698098D8);
                        d = FF(d, a, b, c, x[k + 9], 12, 0x8B44F7AF);
                        c = FF(c, d, a, b, x[k + 10], 17, 0xFFFF5BB1);
                        b = FF(b, c, d, a, x[k + 11], 22, 0x895CD7BE);
                        a = FF(a, b, c, d, x[k + 12], 7, 0x6B901122);
                        d = FF(d, a, b, c, x[k + 13], 12, 0xFD987193);
                        c = FF(c, d, a, b, x[k + 14], 17, 0xA679438E);
                        b = FF(b, c, d, a, x[k + 15], 22, 0x49B40821);
                        a = GG(a, b, c, d, x[k + 1], 5, 0xF61E2562);
                        d = GG(d, a, b, c, x[k + 6], 9, 0xC040B340);
                        c = GG(c, d, a, b, x[k + 11], 14, 0x265E5A51);
                        b = GG(b, c, d, a, x[k + 0], 20, 0xE9B6C7AA);
                        a = GG(a, b, c, d, x[k + 5], 5, 0xD62F105D);
                        d = GG(d, a, b, c, x[k + 10], 9, 0x2441453);
                        c = GG(c, d, a, b, x[k + 15], 14, 0xD8A1E681);
                        b = GG(b, c, d, a, x[k + 4], 20, 0xE7D3FBC8);
                        a = GG(a, b, c, d, x[k + 9], 5, 0x21E1CDE6);
                        d = GG(d, a, b, c, x[k + 14], 9, 0xC33707D6);
                        c = GG(c, d, a, b, x[k + 3], 14, 0xF4D50D87);
                        b = GG(b, c, d, a, x[k + 8], 20, 0x455A14ED);
                        a = GG(a, b, c, d, x[k + 13], 5, 0xA9E3E905);
                        d = GG(d, a, b, c, x[k + 2], 9, 0xFCEFA3F8);
                        c = GG(c, d, a, b, x[k + 7], 14, 0x676F02D9);
                        b = GG(b, c, d, a, x[k + 12], 20, 0x8D2A4C8A);
                        a = HH(a, b, c, d, x[k + 5], 4, 0xFFFA3942);
                        d = HH(d, a, b, c, x[k + 8], 11, 0x8771F681);
                        c = HH(c, d, a, b, x[k + 11], 16, 0x6D9D6122);
                        b = HH(b, c, d, a, x[k + 14], 23, 0xFDE5380C);
                        a = HH(a, b, c, d, x[k + 1], 4, 0xA4BEEA44);
                        d = HH(d, a, b, c, x[k + 4], 11, 0x4BDECFA9);
                        c = HH(c, d, a, b, x[k + 7], 16, 0xF6BB4B60);
                        b = HH(b, c, d, a, x[k + 10], 23, 0xBEBFBC70);
                        a = HH(a, b, c, d, x[k + 13], 4, 0x289B7EC6);
                        d = HH(d, a, b, c, x[k + 0], 11, 0xEAA127FA);
                        c = HH(c, d, a, b, x[k + 3], 16, 0xD4EF3085);
                        b = HH(b, c, d, a, x[k + 6], 23, 0x4881D05);
                        a = HH(a, b, c, d, x[k + 9], 4, 0xD9D4D039);
                        d = HH(d, a, b, c, x[k + 12], 11, 0xE6DB99E5);
                        c = HH(c, d, a, b, x[k + 15], 16, 0x1FA27CF8);
                        b = HH(b, c, d, a, x[k + 2], 23, 0xC4AC5665);
                        a = II(a, b, c, d, x[k + 0], 6, 0xF4292244);
                        d = II(d, a, b, c, x[k + 7], 10, 0x432AFF97);
                        c = II(c, d, a, b, x[k + 14], 15, 0xAB9423A7);
                        b = II(b, c, d, a, x[k + 5], 21, 0xFC93A039);
                        a = II(a, b, c, d, x[k + 12], 6, 0x655B59C3);
                        d = II(d, a, b, c, x[k + 3], 10, 0x8F0CCC92);
                        c = II(c, d, a, b, x[k + 10], 15, 0xFFEFF47D);
                        b = II(b, c, d, a, x[k + 1], 21, 0x85845DD1);
                        a = II(a, b, c, d, x[k + 8], 6, 0x6FA87E4F);
                        d = II(d, a, b, c, x[k + 15], 10, 0xFE2CE6E0);
                        c = II(c, d, a, b, x[k + 6], 15, 0xA3014314);
                        b = II(b, c, d, a, x[k + 13], 21, 0x4E0811A1);
                        a = II(a, b, c, d, x[k + 4], 6, 0xF7537E82);
                        d = II(d, a, b, c, x[k + 11], 10, 0xBD3AF235);
                        c = II(c, d, a, b, x[k + 2], 15, 0x2AD7D2BB);
                        b = II(b, c, d, a, x[k + 9], 21, 0xEB86D391);
                        a = addUnsigned(a, AA);
                        b = addUnsigned(b, BB);
                        c = addUnsigned(c, CC);
                        d = addUnsigned(d, DD);
                    }

                    return (wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d)).toLowerCase();
                };

                // 简单的 XOR 加密/解密
                const encryptData = (data, key) => {
                    const jsonStr = JSON.stringify(data);
                    let encrypted = '';
                    for (let i = 0; i < jsonStr.length; i++) {
                        encrypted += String.fromCharCode(jsonStr.charCodeAt(i) ^ key.charCodeAt(i % key.length));
                    }
                    return btoa(unescape(encodeURIComponent(encrypted))); // Base64编码
                };

                const decryptData = (encryptedStr, key) => {
                    try {
                        const encrypted = decodeURIComponent(escape(atob(encryptedStr))); // Base64解码
                        let decrypted = '';
                        for (let i = 0; i < encrypted.length; i++) {
                            decrypted += String.fromCharCode(encrypted.charCodeAt(i) ^ key.charCodeAt(i % key.length));
                        }
                        return JSON.parse(decrypted);
                    } catch (error) {
                        throw new Error('解密失败：' + error.message);
                    }
                };

                // 默认文本
                const defaultTexts = {
                    start: '开始',
                    process: '过程',
                    decision: '判断?',
                    end: '结束'
                };

                // 创建节点
                const createNode = (type, x, y) => {
                    const node = {
                        id: `node_${++nodeCounter.value}`,
                        type,
                        x,
                        y,
                        text: defaultTexts[type]
                    };
                    nodes.value.push(node);
                    saveState();
                };

                // 处理节点文本键盘事件
                const handleNodeTextKeydown = (event, node) => {
                    // Enter 或 Alt+Enter 换行（contenteditable）
                    if (event.key === 'Enter') {
                        event.preventDefault();

                        // 保存当前光标位置
                        const element = event.target;
                        const cursorPos = saveCursorPosition(element);

                        // 在contenteditable中插入<br>标签实现换行
                        document.execCommand('insertLineBreak');

                        // 更新节点文本
                        node.text = element.innerHTML;

                        // 恢复光标位置（在换行符之后，所以+1）
                        nextTick(() => {
                            restoreCursorPosition(element, cursorPos + 1);
                        });

                        return;
                    }
                };

                // 开始编辑节点文本
                const startEditingNode = (event, node) => {
                    event.stopPropagation();
                    editingNodeId.value = node.id;
                    nextTick(() => {
                        if (nodeInput.value && nodeInput.value.length > 0) {
                            const input = Array.isArray(nodeInput.value) ? nodeInput.value[0] : nodeInput.value;
                            if (input) {
                                input.focus();
                                input.select();
                            }
                        }
                    });
                };

                // 停止编辑节点文本
                const stopEditingNode = () => {
                    editingNodeId.value = null;
                    saveState();
                };

                // 删除节点
                const deleteNode = (nodeId) => {
                    // 删除相关连接
                    connections.value = connections.value.filter(
                        conn => conn.start !== nodeId && conn.end !== nodeId
                    );
                    // 删除节点
                    nodes.value = nodes.value.filter(node => node.id !== nodeId);
                    selectedNodes.value.delete(nodeId);
                    saveState();
                };

                // 删除选中项
                const deleteSelected = () => {
                    if (selectedNodes.value.size > 0) {
                        selectedNodes.value.forEach(nodeId => {
                            deleteNode(nodeId);
                        });
                        selectedNodes.value.clear();
                    } else if (selectedConnection.value) {
                        connections.value = connections.value.filter(
                            conn => conn.id !== selectedConnection.value
                        );
                        selectedConnection.value = null;
                        saveState();
                    }
                };

                // 节点右键菜单
                const onNodeContextMenu = (event, node) => {
                    event.preventDefault();
                    event.stopPropagation();

                    // 只有过程节点才能右键
                    if (node.type !== 'process') {
                        return;
                    }

                    contextMenu.visible = true;
                    contextMenu.x = event.clientX;
                    contextMenu.y = event.clientY;
                    contextMenu.node = node;
                };

                // 关闭右键菜单
                const closeContextMenu = () => {
                    contextMenu.visible = false;
                    contextMenu.node = null;
                };

                // 创建子流程
                const createSubprocess = () => {
                    if (!contextMenu.node || contextMenu.node.subprocessId) {
                        closeContextMenu();
                        return;
                    }

                    const node = contextMenu.node;
                    const subprocessId = `subprocess_${Date.now()}`;

                    // 设置节点的子流程 ID
                    node.subprocessId = subprocessId;

                    // 初始化子流程数据
                    subprocesses.value[subprocessId] = {
                        nodes: [],
                        connections: [],
                        nodeCounter: 0,
                        connectionCounter: 0
                    };

                    saveState();
                    closeContextMenu();

                    // 自动进入新建的子流程
                    openSubprocessModal(node);
                };

                // 导入子流程
                const importSubprocess = () => {
                    if (!contextMenu.node || contextMenu.node.subprocessId) {
                        closeContextMenu();
                        return;
                    }

                    // 保存节点引用
                    contextMenu.pendingNode = contextMenu.node;

                    // 触发文件选择
                    if (subprocessFileInput.value) {
                        subprocessFileInput.value.click();
                    }

                    closeContextMenu();
                };

                // 处理子流程文件加载
                const handleSubprocessFileLoad = (event) => {
                    const file = event.target.files[0];
                    if (!file || !contextMenu.pendingNode) {
                        event.target.value = '';
                        return;
                    }

                    const node = contextMenu.pendingNode;
                    const reader = new FileReader();

                    reader.onload = (e) => {
                        try {
                            const fileContent = e.target.result;
                            let state;

                            // 尝试解密文件
                            try {
                                const decryptedContent = decryptData(fileContent, ENCRYPTION_KEY);
                                const contentObj = decryptedContent;
                                const calculatedChecksum = md5(contentObj.content);

                                if (calculatedChecksum !== contentObj.checksum) {
                                    alert('❌ 文件校验失败！\n\n文件可能已被篡改或损坏。');
                                    event.target.value = '';
                                    return;
                                }

                                const parsedContent = JSON.parse(contentObj.content);
                                state = parsedContent.data;
                            } catch (decryptError) {
                                // 尝试作为JSON读取
                                try {
                                    const jsonContent = JSON.parse(fileContent);
                                    if (jsonContent.version && jsonContent.data) {
                                        state = jsonContent.data;
                                    } else {
                                        state = jsonContent;
                                    }
                                } catch (jsonError) {
                                    alert('❌ 文件格式错误！\n\n无法解密或解析文件。');
                                    event.target.value = '';
                                    return;
                                }
                            }

                            // 创建子流程ID
                            const subprocessId = `subprocess_${Date.now()}`;

                            // 设置节点的子流程ID
                            node.subprocessId = subprocessId;

                            // 将导入的数据保存为子流程
                            subprocesses.value[subprocessId] = {
                                nodes: state.nodes || [],
                                connections: state.connections || [],
                                nodeCounter: state.nodeCounter || 0,
                                connectionCounter: state.connectionCounter || 0,
                                subprocesses: state.subprocesses || {}
                            };

                            saveState();
                            alert('✅ 子流程导入成功！\n\n节点数: ' + (state.nodes ? state.nodes.length : 0) + '\n连接数: ' + (state.connections ? state.connections.length : 0));
                        } catch (error) {
                            alert('❌ 文件读取错误！\n\n' + error.message);
                        }
                    };

                    reader.readAsText(file);
                    event.target.value = '';
                };

                // 进入子流程（弹窗模式）
                const openSubprocessModal = (node) => {
                    if (!node.subprocessId) return;

                    // 保存当前状态到栈中（可能是主流程或父级子流程）
                    if (currentSubprocess.value) {
                        // 如果已经在子流程中，说明是嵌套子流程，保存到子流程数据中
                        const subprocess = subprocesses.value[currentSubprocess.value.subprocessId];
                        if (subprocess) {
                            subprocess.nodes = nodes.value;
                            subprocess.connections = connections.value;
                            subprocess.nodeCounter = nodeCounter.value;
                            subprocess.connectionCounter = connectionCounter.value;
                        }
                        // 将当前子流程压入栈
                        subprocessStack.value.push({
                            parentNode: currentSubprocess.value.parentNode,
                            subprocessId: currentSubprocess.value.subprocessId,
                            nodes: nodes.value,
                            connections: connections.value,
                            nodeCounter: nodeCounter.value,
                            connectionCounter: connectionCounter.value
                        });
                    } else {
                        // 如果在主流程中，将主流程状态压入栈
                        subprocessStack.value.push({
                            parentNode: null, // 主流程没有父节点
                            subprocessId: null,
                            nodes: nodes.value,
                            connections: connections.value,
                            nodeCounter: nodeCounter.value,
                            connectionCounter: connectionCounter.value
                        });
                    }

                    // 加载子流程数据
                    const subprocess = subprocesses.value[node.subprocessId];
                    if (subprocess) {
                        nodes.value = subprocess.nodes || [];
                        connections.value = subprocess.connections || [];
                        nodeCounter.value = subprocess.nodeCounter || 0;
                        connectionCounter.value = subprocess.connectionCounter || 0;
                    } else {
                        // 新建空子流程
                        nodes.value = [];
                        connections.value = [];
                        nodeCounter.value = 0;
                        connectionCounter.value = 0;
                    }

                    // 打开弹窗
                    subprocessModal.visible = true;
                    subprocessModal.parentNode = node;
                    subprocessModal.subprocessId = node.subprocessId;

                    // 设置当前子流程上下文
                    currentSubprocess.value = {
                        parentNode: node,
                        subprocessId: node.subprocessId
                    };

                    closeContextMenu();

                    // 在下一帧初始化子流程容器的滚动监听
                    nextTick(() => {
                        const subContainers = document.querySelectorAll('.subprocess-body .canvas-container');
                        subContainers.forEach(container => {
                            // 初始化滚动位置到中心
                            container.scrollLeft = (10000 * zoom.value - container.clientWidth) / 2;
                            container.scrollTop = (10000 * zoom.value - container.clientHeight) / 2;

                            // 添加滚动事件监听
                            container.addEventListener('scroll', updateScrollPosition);

                            // 初始化滚动位置状态
                            updateScrollPosition();
                        });
                    });
                };

                // 关闭子流程弹窗（返回上一级）
                const closeSubprocessModal = () => {
                    if (!currentSubprocess.value) return;

                    // 移除子流程容器的滚动监听
                    const subContainers = document.querySelectorAll('.subprocess-body .canvas-container');
                    subContainers.forEach(container => {
                        container.removeEventListener('scroll', updateScrollPosition);
                    });

                    // 保存当前子流程数据
                    const subprocess = subprocesses.value[currentSubprocess.value.subprocessId];
                    if (!subprocess) {
                        subprocesses.value[currentSubprocess.value.subprocessId] = {};
                    }
                    subprocesses.value[currentSubprocess.value.subprocessId].nodes = nodes.value;
                    subprocesses.value[currentSubprocess.value.subprocessId].connections = connections.value;
                    subprocesses.value[currentSubprocess.value.subprocessId].nodeCounter = nodeCounter.value;
                    subprocesses.value[currentSubprocess.value.subprocessId].connectionCounter = connectionCounter.value;

                    // 从栈中恢复上一级流程
                    if (subprocessStack.value.length > 0) {
                        const previousState = subprocessStack.value.pop();
                        nodes.value = previousState.nodes;
                        connections.value = previousState.connections;
                        nodeCounter.value = previousState.nodeCounter;
                        connectionCounter.value = previousState.connectionCounter;

                        if (previousState.subprocessId) {
                            // 恢复到父级子流程
                            currentSubprocess.value = {
                                parentNode: previousState.parentNode,
                                subprocessId: previousState.subprocessId
                            };
                            subprocessModal.parentNode = previousState.parentNode;
                            subprocessModal.subprocessId = previousState.subprocessId;
                            // 保持弹窗打开
                        } else {
                            // 恢复到主流程
                            currentSubprocess.value = null;
                            subprocessModal.visible = false;
                            subprocessModal.parentNode = null;
                            subprocessModal.subprocessId = null;
                        }
                    } else {
                        // 栈为空，返回主流程（兜底逻辑）
                        loadMainFlow();
                        currentSubprocess.value = null;
                        subprocessModal.visible = false;
                        subprocessModal.parentNode = null;
                        subprocessModal.subprocessId = null;
                    }

                    saveCurrentFlow();

                    // 恢复滚动监听
                    nextTick(() => {
                        updateScrollPosition();
                    });
                };

                // 加载主流程
                const loadMainFlow = () => {
                    if (currentFlowId.value) {
                        const flow = flowDirectory.value.find(f => f.id === currentFlowId.value);
                        if (flow) {
                            nodes.value = flow.data.nodes || [];
                            connections.value = flow.data.connections || [];
                            nodeCounter.value = flow.data.nodeCounter || 0;
                            connectionCounter.value = flow.data.connectionCounter || 0;
                            subprocesses.value = flow.data.subprocesses || {};
                        }
                    }
                };

                // 进入子流程（保留旧版本兼容）
                const enterSubprocess = (node) => {
                    openSubprocessModal(node);
                };

                // 返回上一级流程（可能是父级子流程或主流程）
                const exitSubprocess = () => {
                    closeSubprocessModal();
                };

                // 形状拖拽开始
                const onShapeDragStart = (event, type) => {
                    event.dataTransfer.setData('nodeType', type);
                };

                // 画布放置
                const onCanvasDrop = (event) => {
                    event.preventDefault();
                    const nodeType = event.dataTransfer.getData('nodeType');
                    // 获取当前活动的容器（子流程或主流程）
                    let container = document.querySelector('.subprocess-body .canvas-container');
                    if (!container) {
                        container = document.querySelector('.canvas-container');
                    }

                    // 获取容器的位置信息
                    const containerRect = container.getBoundingClientRect();

                    // 计算鼠标在容器内的位置（考虑容器边界）
                    const mouseXInViewport = event.clientX - containerRect.left;
                    const mouseYInViewport = event.clientY - containerRect.top;

                    // 转换为画布上的实际坐标（加上滚动偏移，除以缩放比例）
                    const canvasX = (mouseXInViewport + container.scrollLeft) / zoom.value;
                    const canvasY = (mouseYInViewport + container.scrollTop) / zoom.value;

                    // 获取节点尺寸，使节点中心对齐鼠标位置
                    const nodeSize = getNodeSize(nodeType);
                    const finalX = canvasX - nodeSize.width / 2;
                    const finalY = canvasY - nodeSize.height / 2;

                    createNode(nodeType, finalX, finalY);
                };

                // 节点鼠标按下
                const onNodeMouseDown = (event, node) => {
                    event.stopPropagation();

                    // Ctrl+点击：复制节点
                    if (event.ctrlKey || event.metaKey) {
                        // 如果是 Ctrl+点击，复制节点
                        const offsetX = 20; // 复制偏移
                        const offsetY = 20;

                        const newNode = {
                            id: `node_${++nodeCounter.value}`,
                            type: node.type,
                            x: node.x + offsetX,
                            y: node.y + offsetY,
                            text: node.text
                        };

                        nodes.value.push(newNode);

                        // 清除选择并选中新节点
                        selectedNodes.value.clear();
                        selectedNodes.value.add(newNode.id);

                        saveState();

                        // 不开始拖拽，直接返回
                        return;
                    }

                    // 普通点击：选择节点
                    if (!selectedNodes.value.has(node.id)) {
                        selectedNodes.value.clear();
                        selectedNodes.value.add(node.id);
                    }

                    // 开始拖拽
                    isDragging.value = true;

                    // 获取当前活动的容器（子流程或主流程）
                    let container = document.querySelector('.subprocess-body .canvas-container');
                    if (!container) {
                        container = document.querySelector('.canvas-container');
                    }

                    const rect = event.currentTarget.closest('.canvas').getBoundingClientRect();
                    dragStartPos.value = {
                        x: (event.clientX - rect.left + container.scrollLeft) / zoom.value,
                        y: (event.clientY - rect.top + container.scrollTop) / zoom.value
                    };

                    // 保存所有选中节点的初始位置
                    dragNodeStartPositions.value.clear();
                    selectedNodes.value.forEach(nodeId => {
                        const n = nodes.value.find(node => node.id === nodeId);
                        if (n) {
                            dragNodeStartPositions.value.set(nodeId, { x: n.x, y: n.y });
                        }
                    });
                };

                // 画布鼠标按下（框选或拖动画布）
                const onCanvasMouseDown = (event) => {
                    if (event.target.classList.contains('canvas')) {
                        // 获取当前活动的容器（子流程或主流程）
                        let container = document.querySelector('.subprocess-body .canvas-container');
                        if (!container) {
                            container = document.querySelector('.canvas-container');
                        }

                        // 默认拖动画布模式（或按住空格键）
                        if (currentMode.value === 'pan' || event.button === 1) { // 中键拖动
                            isPanning.value = true;
                            panStart.value = {
                                x: event.clientX + container.scrollLeft,
                                y: event.clientY + container.scrollTop
                            };
                            container.style.cursor = 'grabbing';
                            event.preventDefault();
                            return;
                        }

                        // 框选模式（需要按住 Shift 键）
                        if (event.shiftKey) {
                            const rect = event.currentTarget.getBoundingClientRect();
                            const x = (event.clientX - rect.left + container.scrollLeft) / zoom.value;
                            const y = (event.clientY - rect.top + container.scrollTop) / zoom.value;

                            isSelecting.value = true;
                            selectionRect.startX = x;
                            selectionRect.startY = y;
                            selectionRect.left = x;
                            selectionRect.top = y;
                            selectionRect.width = 0;
                            selectionRect.height = 0;
                            selectionRect.visible = true;

                            if (!event.ctrlKey && !event.metaKey) {
                                selectedNodes.value.clear();
                                selectedConnection.value = null;
                            }
                        }
                    }
                };

                // 画布鼠标移动
                const onCanvasMouseMove = (event) => {
                    const canvas = event.currentTarget;
                    const rect = canvas.getBoundingClientRect();
                    // 获取当前活动的容器（子流程或主流程）
                    let container = document.querySelector('.subprocess-body .canvas-container');
                    if (!container) {
                        container = document.querySelector('.canvas-container');
                    }

                    // 拖动画布
                    if (isPanning.value) {
                        container.scrollLeft = panStart.value.x - event.clientX;
                        container.scrollTop = panStart.value.y - event.clientY;
                        updateScrollPosition(); // 更新缩略图
                        return;
                    }

                    const currentX = (event.clientX - rect.left + container.scrollLeft) / zoom.value;
                    const currentY = (event.clientY - rect.top + container.scrollTop) / zoom.value;

                    if (isSelecting.value) {
                        // 更新框选矩形
                        const left = Math.min(selectionRect.startX, currentX);
                        const top = Math.min(selectionRect.startY, currentY);
                        const width = Math.abs(currentX - selectionRect.startX);
                        const height = Math.abs(currentY - selectionRect.startY);

                        selectionRect.left = left;
                        selectionRect.top = top;
                        selectionRect.width = width;
                        selectionRect.height = height;

                        // 检测框选范围内的节点
                        nodes.value.forEach(node => {
                            const nodeRight = node.x + 100; // 近似宽度
                            const nodeBottom = node.y + 60; // 近似高度
                            const isIntersecting = !(
                                nodeRight < left ||
                                node.x > left + width ||
                                nodeBottom < top ||
                                node.y > top + height
                            );

                            if (isIntersecting) {
                                selectedNodes.value.add(node.id);
                            }
                        });
                    } else if (isDragging.value && selectedNodes.value.size > 0) {
                        // 拖拽节点
                        const deltaX = currentX - dragStartPos.value.x;
                        const deltaY = currentY - dragStartPos.value.y;

                        selectedNodes.value.forEach(nodeId => {
                            const node = nodes.value.find(n => n.id === nodeId);
                            const startPos = dragNodeStartPositions.value.get(nodeId);
                            if (node && startPos) {
                                let newX = startPos.x + deltaX;
                                let newY = startPos.y + deltaY;

                                // Shift + 拖拽：如果节点只有一个连线，自动对齐
                                if (event.shiftKey && selectedNodes.value.size === 1) {
                                    // 查找与该节点相关的连接
                                    const nodeConnections = connections.value.filter(
                                        conn => conn.start === nodeId || conn.end === nodeId
                                    );

                                    // 如果只有一个连接
                                    if (nodeConnections.length === 1) {
                                        const conn = nodeConnections[0];
                                        // 找到连接的另一端节点
                                        const otherNodeId = conn.start === nodeId ? conn.end : conn.start;
                                        const otherNode = nodes.value.find(n => n.id === otherNodeId);

                                        if (otherNode) {
                                            // 获取两个节点的中心点
                                            const nodeSize = getNodeSize(node.type, nodeId);
                                            const otherNodeSize = getNodeSize(otherNode.type, otherNodeId);

                                            const nodeCenterX = newX + nodeSize.width / 2;
                                            const nodeCenterY = newY + nodeSize.height / 2;
                                            const otherCenterX = otherNode.x + otherNodeSize.width / 2;
                                            const otherCenterY = otherNode.y + otherNodeSize.height / 2;

                                            // 计算距离
                                            const dx = Math.abs(nodeCenterX - otherCenterX);
                                            const dy = Math.abs(nodeCenterY - otherCenterY);

                                            // 根据距离判断应该对齐x轴还是y轴
                                            if (dx > dy) {
                                                // 水平距离更大，对齐y轴（保持水平）
                                                newY = otherNode.y + (otherNodeSize.height - nodeSize.height) / 2;
                                            } else {
                                                // 垂直距离更大，对齐x轴（保持垂直）
                                                newX = otherNode.x + (otherNodeSize.width - nodeSize.width) / 2;
                                            }
                                        }
                                    }
                                }

                                node.x = newX;
                                node.y = newY;
                            }
                        });
                    }
                };

                // 画布鼠标抬起
                const onCanvasMouseUp = (event) => {
                    // 获取当前活动的容器（子流程或主流程）
                    let container = document.querySelector('.subprocess-body .canvas-container');
                    if (!container) {
                        container = document.querySelector('.canvas-container');
                    }

                    if (isPanning.value) {
                        isPanning.value = false;
                        container.style.cursor = 'grab';
                    }

                    if (isSelecting.value) {
                        isSelecting.value = false;
                        selectionRect.visible = false;
                    }

                    if (isDragging.value) {
                        isDragging.value = false;
                        dragNodeStartPositions.value.clear();
                        saveState();
                    }
                };

                // 开始连接
                const startConnection = (event, node, direction) => {
                    event.stopPropagation();
                    event.preventDefault();
                    isConnecting.value = true;
                    connectionStart.value = { node, direction };

                    const handleMouseUp = (e) => {
                        const target = e.target;
                        if (target.classList.contains('connection-point')) {
                            const endNodeElement = target.closest('.flowchart-node');
                            if (endNodeElement) {
                                // 通过 data-node-id 属性获取节点 ID
                                const endNodeId = endNodeElement.getAttribute('data-node-id');
                                const endNode = nodes.value.find(n => n.id === endNodeId);

                                if (endNode && endNode.id !== connectionStart.value.node.id) {
                                    // 创建连接（允许双向连接和重复连接）
                                    const conn = {
                                        id: `conn_${++connectionCounter.value}`,
                                        start: connectionStart.value.node.id,
                                        end: endNode.id,
                                        label: ''
                                    };
                                    connections.value.push(conn);
                                    saveState();
                                }
                            }
                        }

                        isConnecting.value = false;
                        connectionStart.value = null;
                        document.removeEventListener('mouseup', handleMouseUp);
                    };

                    document.addEventListener('mouseup', handleMouseUp);
                };

                // 选择连接
                const selectConnection = (connId) => {
                    selectedConnection.value = connId;
                    selectedNodes.value.clear();
                };

                // 添加连接线标签
                const addConnectionLabel = (event, conn) => {
                    event.stopPropagation();
                    // 如果没有标签，创建默认标签
                    if (!conn.label || conn.label.trim() === '') {
                        conn.label = '';
                    }
                    // 立即开始编辑
                    startEditingLabel(event, conn);
                };

                // 开始编辑标签
                const startEditingLabel = (event, conn) => {
                    event.stopPropagation();
                    editingLabelId.value = conn.id;
                    nextTick(() => {
                        if (labelInput.value && labelInput.value.length > 0) {
                            const input = Array.isArray(labelInput.value) ? labelInput.value[0] : labelInput.value;
                            if (input) {
                                input.focus();
                                input.select();
                            }
                        }
                    });
                };

                // 停止编辑标签
                const stopEditingLabel = () => {
                    editingLabelId.value = null;
                    saveState();
                };

                // 获取连接线标签位置
                const getConnectionLabelStyle = (conn) => {
                    const startNode = nodes.value.find(n => n.id === conn.start);
                    const endNode = nodes.value.find(n => n.id === conn.end);

                    if (!startNode || !endNode) return { display: 'none' };

                    // 获取节点尺寸
                    const startSize = getNodeSize(startNode.type, startNode.id);
                    const endSize = getNodeSize(endNode.type, endNode.id);

                    // 计算节点中心点
                    const startCenterX = startNode.x + startSize.width / 2;
                    const startCenterY = startNode.y + startSize.height / 2;
                    const endCenterX = endNode.x + endSize.width / 2;
                    const endCenterY = endNode.y + endSize.height / 2;

                    // 标签放在连接线中点
                    const labelX = (startCenterX + endCenterX) / 2;
                    const labelY = (startCenterY + endCenterY) / 2;

                    return {
                        left: labelX + 'px',
                        top: labelY + 'px',
                        transform: 'translate(-50%, -50%)'
                    };
                };

                // 计算边缘交点
                const getEdgePoint = (centerX, centerY, width, height, targetX, targetY, nodeType) => {
                    const dx = targetX - centerX;
                    const dy = targetY - centerY;

                    if (nodeType === 'start' || nodeType === 'end') {
                        // 椭圆节点 - 使用椭圆方程计算交点
                        const rx = width / 2;  // x轴半径
                        const ry = height / 2; // y轴半径

                        // 如果目标点就是中心点，返回中心
                        if (dx === 0 && dy === 0) {
                            return { x: centerX, y: centerY };
                        }

                        // 使用椭圆参数方程
                        // 椭圆上的点: (rx*cos(θ), ry*sin(θ))
                        // 从中心指向目标点的角度
                        const angle = Math.atan2(dy, dx);

                        // 椭圆上对应角度的点
                        return {
                            x: centerX + rx * Math.cos(angle),
                            y: centerY + ry * Math.sin(angle)
                        };
                    } else if (nodeType === 'decision') {
                        // 菱形节点 - 菱形的四个顶点：上(0, -hh), 右(hw, 0), 下(0, hh), 左(-hw, 0)
                        const hw = width / 2;
                        const hh = height / 2;

                        // 如果目标点就是中心点，返回中心
                        if (dx === 0 && dy === 0) {
                            return { x: centerX, y: centerY };
                        }

                        // 使用菱形的参数方程计算交点
                        // 菱形的曼哈顿距离归一化
                        const normDist = Math.abs(dx / hw) + Math.abs(dy / hh);

                        return {
                            x: centerX + dx / normDist,
                            y: centerY + dy / normDist
                        };
                    } else {
                        // 矩形节点
                        const hw = width / 2;
                        const hh = height / 2;

                        // 如果目标点就是中心点，返回中心
                        if (dx === 0 && dy === 0) {
                            return { x: centerX, y: centerY };
                        }

                        // 计算射线与矩形四条边的交点
                        // 使用参数化射线: P = center + t * direction
                        let tMin = Infinity;
                        let edgeX = centerX;
                        let edgeY = centerY;

                        // 右边: x = centerX + hw
                        if (dx > 0) {
                            const t = hw / dx;
                            const y = centerY + t * dy;
                            if (Math.abs(y - centerY) <= hh && t < tMin) {
                                tMin = t;
                                edgeX = centerX + hw;
                                edgeY = y;
                            }
                        }

                        // 左边: x = centerX - hw
                        if (dx < 0) {
                            const t = -hw / dx;
                            const y = centerY + t * dy;
                            if (Math.abs(y - centerY) <= hh && t < tMin) {
                                tMin = t;
                                edgeX = centerX - hw;
                                edgeY = y;
                            }
                        }

                        // 下边: y = centerY + hh
                        if (dy > 0) {
                            const t = hh / dy;
                            const x = centerX + t * dx;
                            if (Math.abs(x - centerX) <= hw && t < tMin) {
                                tMin = t;
                                edgeX = x;
                                edgeY = centerY + hh;
                            }
                        }

                        // 上边: y = centerY - hh
                        if (dy < 0) {
                            const t = -hh / dy;
                            const x = centerX + t * dx;
                            if (Math.abs(x - centerX) <= hw && t < tMin) {
                                tMin = t;
                                edgeX = x;
                                edgeY = centerY - hh;
                            }
                        }

                        return { x: edgeX, y: edgeY };
                    }
                };

                // 获取节点尺寸（包含 padding 和 border）
                const getNodeSize = (nodeType, nodeId) => {
                    // 尝试从DOM获取实际尺寸
                    if (nodeId) {
                        const nodeElement = document.querySelector(`[data-node-id="${nodeId}"]`);
                        if (nodeElement) {
                            const rect = nodeElement.getBoundingClientRect();
                            return {
                                width: rect.width / zoom.value,
                                height: rect.height / zoom.value
                            };
                        }
                    }

                    // 回退到默认尺寸
                    if (nodeType === 'start') return { width: 120, height: 80 };
                    if (nodeType === 'end') return { width: 100, height: 100 };
                    if (nodeType === 'decision') return { width: 120, height: 80 };
                    return { width: 120, height: 64 };
                };

                // 获取可见的连接（处理双向连接）
                const getVisibleConnections = () => {
                    const visibleConns = [];
                    const processedPairs = new Set();

                    connections.value.forEach(conn => {
                        const pairKey1 = `${conn.start}-${conn.end}`;
                        const pairKey2 = `${conn.end}-${conn.start}`;

                        // 检查此连接对是否已处理
                        if (processedPairs.has(pairKey1) || processedPairs.has(pairKey2)) {
                            return;
                        }

                        // 检查是否存在反向连接
                        const reverseConn = connections.value.find(
                            c => c.start === conn.end && c.end === conn.start
                        );

                        if (reverseConn) {
                            // 标记为双向并添加到可见列表
                            visibleConns.push({
                                ...conn,
                                isBidirectional: true
                            });
                            processedPairs.add(pairKey1);
                            processedPairs.add(pairKey2);
                        } else {
                            // 单向连接
                            visibleConns.push({
                                ...conn,
                                isBidirectional: false
                            });
                            processedPairs.add(pairKey1);
                        }
                    });

                    return visibleConns;
                };

                // 检测线段是否与节点相交
                const lineIntersectsNode = (x1, y1, x2, y2, node, nodeId) => {
                    const size = getNodeSize(node.type, nodeId);
                    const nodeLeft = node.x;
                    const nodeRight = node.x + size.width;
                    const nodeTop = node.y;
                    const nodeBottom = node.y + size.height;

                    // 检查线段的边界框是否与节点相交
                    const lineLeft = Math.min(x1, x2);
                    const lineRight = Math.max(x1, x2);
                    const lineTop = Math.min(y1, y2);
                    const lineBottom = Math.max(y1, y2);

                    // 如果线段的边界框不与节点相交，则线段肯定不穿过节点
                    if (lineRight < nodeLeft || lineLeft > nodeRight ||
                        lineBottom < nodeTop || lineTop > nodeBottom) {
                        return false;
                    }

                    // 检查线段是否真的穿过节点（而不仅仅是边界框相交）
                    // 使用线段与矩形的精确相交检测

                    // 如果线段的任一端点在节点内部，返回true
                    if ((x1 >= nodeLeft && x1 <= nodeRight && y1 >= nodeTop && y1 <= nodeBottom) ||
                        (x2 >= nodeLeft && x2 <= nodeRight && y2 >= nodeTop && y2 <= nodeBottom)) {
                        return true;
                    }

                    // 检查线段是否与矩形的四条边相交
                    const intersectsEdge = (px1, py1, px2, py2, ex1, ey1, ex2, ey2) => {
                        const denom = ((ey2 - ey1) * (px2 - px1) - (ex2 - ex1) * (py2 - py1));
                        if (Math.abs(denom) < 0.0001) return false;

                        const ua = ((ex2 - ex1) * (py1 - ey1) - (ey2 - ey1) * (px1 - ex1)) / denom;
                        const ub = ((px2 - px1) * (py1 - ey1) - (py2 - py1) * (px1 - ex1)) / denom;

                        return (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1);
                    };

                    // 检查与四条边的相交
                    if (intersectsEdge(x1, y1, x2, y2, nodeLeft, nodeTop, nodeRight, nodeTop) ||     // 上边
                        intersectsEdge(x1, y1, x2, y2, nodeRight, nodeTop, nodeRight, nodeBottom) || // 右边
                        intersectsEdge(x1, y1, x2, y2, nodeLeft, nodeBottom, nodeRight, nodeBottom) || // 下边
                        intersectsEdge(x1, y1, x2, y2, nodeLeft, nodeTop, nodeLeft, nodeBottom)) {    // 左边
                        return true;
                    }

                    return false;
                };

                // 获取连接路径（允许线条重叠，避开中间节点）
                const getConnectionPath = (conn) => {
                    const startNode = nodes.value.find(n => n.id === conn.start);
                    const endNode = nodes.value.find(n => n.id === conn.end);

                    if (!startNode || !endNode) return '';

                    // 获取节点尺寸（从DOM获取实际尺寸）
                    const startSize = getNodeSize(startNode.type, startNode.id);
                    const endSize = getNodeSize(endNode.type, endNode.id);

                    // 计算节点中心点
                    const startCenterX = startNode.x + startSize.width / 2;
                    const startCenterY = startNode.y + startSize.height / 2;
                    const endCenterX = endNode.x + endSize.width / 2;
                    const endCenterY = endNode.y + endSize.height / 2;

                    // 计算两节点的相对位置
                    const dx = endCenterX - startCenterX;
                    const dy = endCenterY - startCenterY;

                    // 判断是水平还是垂直为主
                    const isHorizontal = Math.abs(dx) > Math.abs(dy);

                    // 计算标准化的边缘点（所有相同方向的连线都使用相同的出发点和到达点）
                    let startEdge, endEdge;

                    if (isHorizontal) {
                        // 水平方向为主
                        if (dx > 0) {
                            // 从右边出发，到左边
                            startEdge = {
                                x: startNode.x + startSize.width,
                                y: startCenterY
                            };
                            endEdge = {
                                x: endNode.x,
                                y: endCenterY
                            };
                        } else {
                            // 从左边出发，到右边
                            startEdge = {
                                x: startNode.x,
                                y: startCenterY
                            };
                            endEdge = {
                                x: endNode.x + endSize.width,
                                y: endCenterY
                            };
                        }
                    } else {
                        // 垂直方向为主
                        if (dy > 0) {
                            // 从下边出发，到上边
                            startEdge = {
                                x: startCenterX,
                                y: startNode.y + startSize.height
                            };
                            endEdge = {
                                x: endCenterX,
                                y: endNode.y
                            };
                        } else {
                            // 从上边出发，到下边
                            startEdge = {
                                x: startCenterX,
                                y: startNode.y
                            };
                            endEdge = {
                                x: endCenterX,
                                y: endNode.y + endSize.height
                            };
                        }
                    }

                    // 箭头偏移量
                    const getArrowOffset = (nodeType) => {
                        if (nodeType === 'start' || nodeType === 'end') {
                            return 6;
                        } else if (nodeType === 'decision') {
                            return 5;
                        } else {
                            return 4;
                        }
                    };

                    const startOffset = getArrowOffset(startNode.type);
                    const endOffset = getArrowOffset(endNode.type);

                    // 计算从起点到终点的方向向量
                    const startToEnd = {
                        x: endEdge.x - startEdge.x,
                        y: endEdge.y - startEdge.y
                    };
                    const startToEndLength = Math.sqrt(startToEnd.x ** 2 + startToEnd.y ** 2);

                    // 归一化方向向量
                    const startToEndNorm = {
                        x: startToEnd.x / startToEndLength,
                        y: startToEnd.y / startToEndLength
                    };

                    // 调整起点和终点，为箭头留出空间
                    const adjustedStartEdge = {
                        x: startEdge.x + startToEndNorm.x * (conn.isBidirectional ? startOffset : 0),
                        y: startEdge.y + startToEndNorm.y * (conn.isBidirectional ? startOffset : 0)
                    };
                    const adjustedEndEdge = {
                        x: endEdge.x - startToEndNorm.x * endOffset,
                        y: endEdge.y - startToEndNorm.y * endOffset
                    };

                    // 检查中间路径是否会穿过其他节点
                    let midX, midY;
                    const obstacleMargin = 20; // 避障边距

                    if (isHorizontal) {
                        midX = adjustedStartEdge.x + (adjustedEndEdge.x - adjustedStartEdge.x) / 2;
                        midY = adjustedStartEdge.y;

                        // 检查是否有节点在路径上
                        let needDetour = false;
                        let detourNode = null;

                        for (const node of nodes.value) {
                            if (node.id === conn.start || node.id === conn.end) continue;

                            // 检查三段线是否穿过节点
                            if (lineIntersectsNode(adjustedStartEdge.x, adjustedStartEdge.y, midX, midY, node, node.id) ||
                                lineIntersectsNode(midX, midY, midX, adjustedEndEdge.y, node, node.id) ||
                                lineIntersectsNode(midX, adjustedEndEdge.y, adjustedEndEdge.x, adjustedEndEdge.y, node, node.id)) {
                                needDetour = true;
                                detourNode = node;
                                break;
                            }
                        }

                        if (needDetour && detourNode) {
                            const nodeSize = getNodeSize(detourNode.type, detourNode.id);
                            const nodeTop = detourNode.y;
                            const nodeBottom = detourNode.y + nodeSize.height;

                            // 判断从上方还是下方绕过
                            const goAbove = adjustedStartEdge.y < nodeTop;
                            const detourY = goAbove ? nodeTop - obstacleMargin : nodeBottom + obstacleMargin;

                            // 创建绕路路径
                            return `M ${adjustedStartEdge.x} ${adjustedStartEdge.y} L ${midX} ${adjustedStartEdge.y} L ${midX} ${detourY} L ${midX} ${adjustedEndEdge.y} L ${adjustedEndEdge.x} ${adjustedEndEdge.y}`;
                        }

                        return `M ${adjustedStartEdge.x} ${adjustedStartEdge.y} L ${midX} ${adjustedStartEdge.y} L ${midX} ${adjustedEndEdge.y} L ${adjustedEndEdge.x} ${adjustedEndEdge.y}`;
                    } else {
                        midX = adjustedStartEdge.x;
                        midY = adjustedStartEdge.y + (adjustedEndEdge.y - adjustedStartEdge.y) / 2;

                        // 检查是否有节点在路径上
                        let needDetour = false;
                        let detourNode = null;

                        for (const node of nodes.value) {
                            if (node.id === conn.start || node.id === conn.end) continue;

                            // 检查三段线是否穿过节点
                            if (lineIntersectsNode(adjustedStartEdge.x, adjustedStartEdge.y, midX, midY, node, node.id) ||
                                lineIntersectsNode(midX, midY, adjustedEndEdge.x, midY, node, node.id) ||
                                lineIntersectsNode(adjustedEndEdge.x, midY, adjustedEndEdge.x, adjustedEndEdge.y, node, node.id)) {
                                needDetour = true;
                                detourNode = node;
                                break;
                            }
                        }

                        if (needDetour && detourNode) {
                            const nodeSize = getNodeSize(detourNode.type, detourNode.id);
                            const nodeLeft = detourNode.x;
                            const nodeRight = detourNode.x + nodeSize.width;

                            // 判断从左方还是右方绕过
                            const goLeft = adjustedStartEdge.x < nodeLeft;
                            const detourX = goLeft ? nodeLeft - obstacleMargin : nodeRight + obstacleMargin;

                            // 创建绕路路径
                            return `M ${adjustedStartEdge.x} ${adjustedStartEdge.y} L ${adjustedStartEdge.x} ${midY} L ${detourX} ${midY} L ${adjustedEndEdge.x} ${midY} L ${adjustedEndEdge.x} ${adjustedEndEdge.y}`;
                        }

                        return `M ${adjustedStartEdge.x} ${adjustedStartEdge.y} L ${adjustedStartEdge.x} ${midY} L ${adjustedEndEdge.x} ${midY} L ${adjustedEndEdge.x} ${adjustedEndEdge.y}`;
                    }
                };

                // ========== 目录管理功能 ==========

                // 切换目录面板
                const toggleDirectory = () => {
                    showDirectory.value = !showDirectory.value;
                };

                // 创建新流程
                const createNewFlow = () => {
                    const flowId = `flow_${Date.now()}`;
                    const newFlow = {
                        id: flowId,
                        name: `新建流程_${flowDirectory.value.length + 1}`,
                        createdAt: Date.now(),
                        updatedAt: Date.now(),
                        data: {
                            nodes: [],
                            connections: [],
                            nodeCounter: 0,
                            connectionCounter: 0,
                            subprocesses: {}
                        }
                    };

                    flowDirectory.value.push(newFlow);
                    saveDirectory();
                    loadFlow(flowId);
                };

                // 保存当前流程
                const saveCurrentFlow = () => {
                    if (!currentFlowId.value) {
                        createNewFlow();
                        return;
                    }

                    const flow = flowDirectory.value.find(f => f.id === currentFlowId.value);
                    if (flow) {
                        // 如果当前在子流程中，先保存子流程数据
                        if (currentSubprocess.value) {
                            const subprocess = subprocesses.value[currentSubprocess.value.subprocessId];
                            if (!subprocess) {
                                subprocesses.value[currentSubprocess.value.subprocessId] = {};
                            }
                            subprocesses.value[currentSubprocess.value.subprocessId].nodes = nodes.value;
                            subprocesses.value[currentSubprocess.value.subprocessId].connections = connections.value;
                            subprocesses.value[currentSubprocess.value.subprocessId].nodeCounter = nodeCounter.value;
                            subprocesses.value[currentSubprocess.value.subprocessId].connectionCounter = connectionCounter.value;

                            // 只保存子流程数据和subprocesses，不改变主流程的nodes和connections
                            flow.data.subprocesses = subprocesses.value;
                        } else {
                            // 在主流程中，正常保存所有数据
                            flow.data = {
                                nodes: nodes.value,
                                connections: connections.value,
                                nodeCounter: nodeCounter.value,
                                connectionCounter: connectionCounter.value,
                                subprocesses: subprocesses.value
                            };
                        }

                        flow.updatedAt = Date.now();
                        saveDirectory();

                        // 显示自动保存提示
                        showAutoSaveIndicator.value = true;
                        setTimeout(() => {
                            showAutoSaveIndicator.value = false;
                        }, 2000);
                    }
                };

                // 加载流程
                const loadFlow = (flowId) => {
                    const flow = flowDirectory.value.find(f => f.id === flowId);
                    if (flow) {
                        currentFlowId.value = flowId;
                        currentFlowName.value = flow.name;
                        nodes.value = flow.data.nodes || [];
                        connections.value = flow.data.connections || [];
                        nodeCounter.value = flow.data.nodeCounter || 0;
                        connectionCounter.value = flow.data.connectionCounter || 0;
                        subprocesses.value = flow.data.subprocesses || {};

                        // 重置历史
                        history.value = [];
                        historyIndex.value = -1;
                        saveState();
                    }
                };

                // 删除流程
                const deleteFlow = (flowId) => {
                    if (confirm('确定要删除这个流程吗？')) {
                        flowDirectory.value = flowDirectory.value.filter(f => f.id !== flowId);
                        saveDirectory();

                        // 如果删除的是当前流程，创建新流程
                        if (currentFlowId.value === flowId) {
                            if (flowDirectory.value.length > 0) {
                                loadFlow(flowDirectory.value[0].id);
                            } else {
                                createNewFlow();
                            }
                        }
                    }
                };

                // 保存目录到 localStorage
                const saveDirectory = () => {
                    try {
                        localStorage.setItem('flowchart_directory', JSON.stringify(flowDirectory.value));
                    } catch (e) {
                        console.error('保存目录失败:', e);
                    }
                };

                // 从 localStorage 加载目录
                const loadDirectory = () => {
                    try {
                        const saved = localStorage.getItem('flowchart_directory');
                        if (saved) {
                            flowDirectory.value = JSON.parse(saved);
                            // 加载最近编辑的流程
                            if (flowDirectory.value.length > 0) {
                                const lastFlow = flowDirectory.value.sort((a, b) => b.updatedAt - a.updatedAt)[0];
                                loadFlow(lastFlow.id);
                            } else {
                                createNewFlow();
                            }
                        } else {
                            createNewFlow();
                        }
                    } catch (e) {
                        console.error('加载目录失败:', e);
                        createNewFlow();
                    }
                };

                // 格式化时间
                const formatTime = (timestamp) => {
                    const date = new Date(timestamp);
                    const now = new Date();
                    const diff = now - date;

                    if (diff < 60000) return '刚刚';
                    if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`;
                    if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`;

                    return date.toLocaleDateString('zh-CN', {
                        month: '2-digit',
                        day: '2-digit',
                        hour: '2-digit',
                        minute: '2-digit'
                    });
                };

                // 启动自动保存
                const startAutoSave = () => {
                    if (autoSaveTimer.value) {
                        clearInterval(autoSaveTimer.value);
                    }

                    // 每30秒自动保存一次
                    autoSaveTimer.value = setInterval(() => {
                        if (currentFlowId.value) {
                            saveCurrentFlow();
                        }
                    }, 30000); // 30秒
                };

                // 停止自动保存
                const stopAutoSave = () => {
                    if (autoSaveTimer.value) {
                        clearInterval(autoSaveTimer.value);
                        autoSaveTimer.value = null;
                    }
                };

                // ========== 结束目录管理功能 ==========

                // 保存状态
                const saveState = () => {
                    const state = JSON.stringify({
                        nodes: nodes.value,
                        connections: connections.value,
                        nodeCounter: nodeCounter.value,
                        connectionCounter: connectionCounter.value,
                        subprocesses: subprocesses.value
                    });

                    history.value = history.value.slice(0, historyIndex.value + 1);
                    history.value.push(state);
                    historyIndex.value++;

                    // 限制历史记录
                    if (history.value.length > 50) {
                        history.value = history.value.slice(-50);
                        historyIndex.value = history.value.length - 1;
                    }
                };

                // 加载状态
                const loadState = (state) => {
                    nodes.value = state.nodes;
                    connections.value = state.connections;
                    nodeCounter.value = state.nodeCounter || 0;
                    connectionCounter.value = state.connectionCounter || 0;
                    subprocesses.value = state.subprocesses || {};
                };

                // 撤销
                const undo = () => {
                    if (historyIndex.value > 0) {
                        historyIndex.value--;
                        loadState(JSON.parse(history.value[historyIndex.value]));
                    }
                };

                // 重做
                const redo = () => {
                    if (historyIndex.value < history.value.length - 1) {
                        historyIndex.value++;
                        loadState(JSON.parse(history.value[historyIndex.value]));
                    }
                };

                // 导出流程（加密版本）
                const exportFlow = () => {
                    let state;
                    let fileName;
                    let exportType;

                    if (currentSubprocess.value) {
                        // 在子流程中，只导出当前子流程
                        state = {
                            nodes: nodes.value,
                            connections: connections.value,
                            subprocesses: {}, // 子流程不包含嵌套的子流程
                            nodeCounter: nodeCounter.value,
                            connectionCounter: connectionCounter.value
                        };
                        const parentNodeText = stripHtmlTags(currentSubprocess.value.parentNode?.text) || '子流程';
                        fileName = `${parentNodeText}_子流程`;
                        exportType = '子流程';
                    } else {
                        // 在主流程中，导出完整流程（包括所有子流程）
                        state = {
                            nodes: nodes.value,
                            connections: connections.value,
                            subprocesses: subprocesses.value,
                            nodeCounter: nodeCounter.value,
                            connectionCounter: connectionCounter.value
                        };
                        fileName = currentFlowName.value || '流程图';
                        exportType = '完整流程';
                    }

                    // 构建原始文件内容
                    const originalContent = {
                        version: '1.0',
                        data: state
                    };

                    // 将整个内容转为JSON字符串
                    const jsonStr = JSON.stringify(originalContent);

                    // 计算MD5校验值
                    const checksum = md5(jsonStr);

                    // 构建带校验值的内容
                    const contentWithChecksum = {
                        checksum: checksum,
                        content: jsonStr
                    };

                    // 加密整个内容（包括校验码）
                    const finalEncrypted = encryptData(contentWithChecksum, ENCRYPTION_KEY);

                    const blob = new Blob([finalEncrypted], { type: 'application/octet-stream' });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = `menu_${fileName}.ywb`;
                    a.click();
                    URL.revokeObjectURL(url);

                    // 提示用户保存到 menu 文件夹
                    setTimeout(() => {
                        const nodeCount = state.nodes ? state.nodes.length : 0;
                        const connCount = state.connections ? state.connections.length : 0;
                        const subCount = Object.keys(state.subprocesses || {}).length;
                        alert(`💾 ${exportType}已导出（已加密）\n\n导出内容：\n- 节点数: ${nodeCount}\n- 连接数: ${connCount}\n- 子流程数: ${subCount}\n\n建议保存位置：\n当前 HTML 文件所在目录的 menu 文件夹\n\n文件名：menu_${fileName}.ywb\n\n✅ 文件已加密并添加MD5校验`);
                    }, 100);
                };

                // 批量导出所有流程
                const exportAllFlows = () => {
                    if (flowDirectory.value.length === 0) {
                        alert('没有可导出的流程');
                        return;
                    }

                    if (!confirm(`确定要导出所有 ${flowDirectory.value.length} 个流程吗？\n\n文件将加密导出并保存到下载文件夹\n建议移动到 menu 文件夹`)) {
                        return;
                    }

                    let exportCount = 0;
                    flowDirectory.value.forEach((flow, index) => {
                        setTimeout(() => {
                            // 构建原始文件内容
                            const originalContent = {
                                version: '1.0',
                                data: flow.data
                            };

                            // 将整个内容转为JSON字符串
                            const jsonStr = JSON.stringify(originalContent);

                            // 计算MD5校验值
                            const checksum = md5(jsonStr);

                            // 构建带校验值的内容
                            const contentWithChecksum = {
                                checksum: checksum,
                                content: jsonStr
                            };

                            // 加密整个内容（包括校验码）
                            const finalEncrypted = encryptData(contentWithChecksum, ENCRYPTION_KEY);

                            const blob = new Blob([finalEncrypted], { type: 'application/octet-stream' });
                            const url = URL.createObjectURL(blob);
                            const a = document.createElement('a');
                            a.href = url;
                            a.download = `menu_${flow.name}.ywb`;
                            a.click();
                            URL.revokeObjectURL(url);
                            exportCount++;

                            if (exportCount === flowDirectory.value.length) {
                                setTimeout(() => {
                                    alert(`✅ 批量导出完成！\n\n已导出 ${exportCount} 个加密流程文件\n\n建议：\n1. 在 HTML 文件夹创建 menu 文件夹\n2. 将所有 menu_*.ywb 文件移动到 menu 文件夹\n\n✅ 所有文件已加密并添加MD5校验`);
                                }, 500);
                            }
                        }, index * 200); // 延迟导出，避免浏览器阻止
                    });
                };

                // 导入流程
                const importFlow = () => {
                    fileInput.value.click();
                };

                const handleFileLoad = (event) => {
                    const file = event.target.files[0];
                    if (file) {
                        const reader = new FileReader();
                        reader.onload = (e) => {
                            try {
                                const fileContent = e.target.result;
                                let state;

                                // 尝试解密文件
                                try {
                                    // 解密整个内容
                                    const decryptedContent = decryptData(fileContent, ENCRYPTION_KEY);

                                    // 验证MD5
                                    const contentObj = decryptedContent;
                                    const calculatedChecksum = md5(contentObj.content);

                                    if (calculatedChecksum !== contentObj.checksum) {
                                        alert('❌ 文件校验失败！\n\n文件可能已被篡改或损坏，无法导入。\n\n请确认文件来源是否可信。');
                                        event.target.value = '';
                                        return;
                                    }

                                    // 解析JSON内容
                                    const parsedContent = JSON.parse(contentObj.content);
                                    state = parsedContent.data;
                                } catch (decryptError) {
                                    // 如果解密失败，尝试作为旧格式的JSON文件读取
                                    try {
                                        const jsonContent = JSON.parse(fileContent);
                                        // 兼容旧的JSON格式
                                        if (jsonContent.version && jsonContent.data) {
                                            state = jsonContent.data;
                                        } else {
                                            state = jsonContent;
                                        }
                                    } catch (jsonError) {
                                        alert('❌ 文件格式错误！\n\n无法解密或解析文件。');
                                        event.target.value = '';
                                        return;
                                    }
                                }

                                // 生成新的流程ID和名称
                                const fileName = file.name.replace('.ywb', '').replace('.json', '').replace(/^menu_/, '');
                                const flowId = fileName || `导入的流程_${Date.now()}`;

                                // 创建新流程并保存到目录
                                const newFlow = {
                                    id: flowId,
                                    name: flowId,
                                    createdAt: Date.now(),
                                    updatedAt: Date.now(),
                                    data: {
                                        nodes: state.nodes || [],
                                        connections: state.connections || [],
                                        nodeCounter: state.nodeCounter || 0,
                                        connectionCounter: state.connectionCounter || 0,
                                        subprocesses: state.subprocesses || {}
                                    }
                                };

                                // 检查是否已存在同名流程
                                const existingIndex = flowDirectory.value.findIndex(f => f.id === flowId);
                                if (existingIndex >= 0) {
                                    if (confirm(`流程 "${flowId}" 已存在，是否覆盖？`)) {
                                        flowDirectory.value[existingIndex] = newFlow;
                                    } else {
                                        event.target.value = '';
                                        return;
                                    }
                                } else {
                                    flowDirectory.value.push(newFlow);
                                }

                                // 保存目录
                                saveDirectory();

                                // 加载导入的流程
                                loadFlow(flowId);

                                alert('✅ 流程导入成功！\n\n节点数: ' + (state.nodes ? state.nodes.length : 0) + '\n连接数: ' + (state.connections ? state.connections.length : 0));
                            } catch (error) {
                                alert('❌ 文件格式错误！\n\n' + error.message);
                            }
                        };
                        reader.readAsText(file);
                    }

                    // 清空文件选择，允许重复导入相同文件
                    event.target.value = '';
                };

                // 打开本地文件夹提示（右键菜单）
                const openLocalFolder = () => {
                    const htmlPath = window.location.pathname.replace(/[^/\\]+$/, '');
                    const message = `💾 建议保存位置：\n${htmlPath}menu\\\n\n📝 使用说明：\n1. 在当前 HTML 文件夹创建 menu 文件夹\n2. 点击"📥 导入流程"导入 JSON 文件\n3. 导出的文件名会自动添加 menu_ 前缀\n4. 将导出的文件保存到 menu 文件夹方便管理\n\n💡 流程数据保存在浏览器 localStorage 中`;
                    alert(message);
                };

                // 清空画布
                const clearAll = () => {
                    if (confirm('确定要清空画布吗？')) {
                        nodes.value = [];
                        connections.value = [];
                        selectedNodes.value.clear();
                        selectedConnection.value = null;
                        saveState();
                    }
                };

                // 缩放控制
                const zoomIn = () => {
                    const container = document.querySelector('.canvas-container');
                    if (!container) return;

                    const oldZoom = zoom.value;
                    const newZoom = Math.min(zoom.value * 1.2, 3);

                    // 计算视口中心在画布上的位置
                    const centerX = (container.scrollLeft + container.clientWidth / 2) / oldZoom;
                    const centerY = (container.scrollTop + container.clientHeight / 2) / oldZoom;

                    zoom.value = newZoom;

                    // 缩放后保持视口中心位置不变
                    nextTick(() => {
                        container.scrollLeft = centerX * newZoom - container.clientWidth / 2;
                        container.scrollTop = centerY * newZoom - container.clientHeight / 2;
                    });
                };

                const zoomOut = () => {
                    const container = document.querySelector('.canvas-container');
                    if (!container) return;

                    const oldZoom = zoom.value;
                    const newZoom = Math.max(zoom.value / 1.2, 0.3);

                    // 计算视口中心在画布上的位置
                    const centerX = (container.scrollLeft + container.clientWidth / 2) / oldZoom;
                    const centerY = (container.scrollTop + container.clientHeight / 2) / oldZoom;

                    zoom.value = newZoom;

                    // 缩放后保持视口中心位置不变
                    nextTick(() => {
                        container.scrollLeft = centerX * newZoom - container.clientWidth / 2;
                        container.scrollTop = centerY * newZoom - container.clientHeight / 2;
                    });
                };

                const fitToView = () => {
                    const container = document.querySelector('.canvas-container');
                    if (!container) return;

                    // 计算当前视口中心
                    const centerX = (container.scrollLeft + container.clientWidth / 2) / zoom.value;
                    const centerY = (container.scrollTop + container.clientHeight / 2) / zoom.value;

                    zoom.value = 1;

                    // 恢复到1倍缩放时保持视口中心
                    nextTick(() => {
                        container.scrollLeft = centerX - container.clientWidth / 2;
                        container.scrollTop = centerY - container.clientHeight / 2;
                    });
                };

                // ========== 节点列表和缩略图功能 ==========

                // 切换节点列表
                const toggleNodeList = () => {
                    showNodeList.value = !showNodeList.value;
                };

                // 切换缩略图
                const toggleMinimap = () => {
                    showMinimap.value = !showMinimap.value;
                };

                // 定位到节点
                const locateNode = (node) => {
                    // 清除当前选择并选中此节点
                    selectedNodes.value.clear();
                    selectedNodes.value.add(node.id);

                    // 滚动画布以显示节点 - 确保节点在视口中央
                    const container = document.querySelector('.canvas-container');
                    if (container) {
                        const nodeSize = getNodeSize(node.type);
                        // 计算节点中心点在缩放后画布上的位置
                        const nodeCenterX = (node.x + nodeSize.width / 2) * zoom.value;
                        const nodeCenterY = (node.y + nodeSize.height / 2) * zoom.value;

                        // 将容器滚动到节点中心
                        container.scrollLeft = nodeCenterX - container.clientWidth / 2;
                        container.scrollTop = nodeCenterY - container.clientHeight / 2;
                    }
                };

                // 节点列表项双击事件 - 如果有子流程则进入
                const onNodeListItemDblClick = (node) => {
                    if (node.type === 'process' && node.subprocessId) {
                        openSubprocessModal(node);
                    }
                };

                // 获取缩略图节点样式
                const getMinimapNodeStyle = (node) => {
                    if (!minimapContent.value || nodes.value.length === 0) return {};

                    // 计算所有节点的边界框
                    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
                    nodes.value.forEach(n => {
                        const size = getNodeSize(n.type);
                        minX = Math.min(minX, n.x);
                        minY = Math.min(minY, n.y);
                        maxX = Math.max(maxX, n.x + size.width);
                        maxY = Math.max(maxY, n.y + size.height);
                    });

                    const contentWidth = maxX - minX + 100;
                    const contentHeight = maxY - minY + 100;
                    const scale = Math.min(280 / contentWidth, 280 / contentHeight, 0.1);

                    const size = getNodeSize(node.type);
                    return {
                        left: ((node.x - minX + 50) * scale) + 'px',
                        top: ((node.y - minY + 50) * scale) + 'px',
                        width: (size.width * scale) + 'px',
                        height: (size.height * scale) + 'px'
                    };
                };

                // 缩略图视口样式
                const minimapViewportStyle = computed(() => {
                    const container = canvasContainer.value;
                    // 使用 scrollPosition 来让视口实时更新
                    const scroll = scrollPosition.value;

                    if (!container || !minimapContent.value || nodes.value.length === 0) {
                        return { display: 'none' };
                    }

                    // 计算边界框
                    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
                    nodes.value.forEach(n => {
                        const size = getNodeSize(n.type);
                        minX = Math.min(minX, n.x);
                        minY = Math.min(minY, n.y);
                        maxX = Math.max(maxX, n.x + size.width);
                        maxY = Math.max(maxY, n.y + size.height);
                    });

                    const contentWidth = maxX - minX + 100;
                    const contentHeight = maxY - minY + 100;
                    const scale = Math.min(280 / contentWidth, 280 / contentHeight, 0.1);

                    const viewportX = (scroll.left / zoom.value - minX + 50) * scale;
                    const viewportY = (scroll.top / zoom.value - minY + 50) * scale;
                    const viewportWidth = (container.clientWidth / zoom.value) * scale;
                    const viewportHeight = (container.clientHeight / zoom.value) * scale;

                    return {
                        left: viewportX + 'px',
                        top: viewportY + 'px',
                        width: viewportWidth + 'px',
                        height: viewportHeight + 'px'
                    };
                });

                // 缩略图点击
                const onMinimapClick = (event) => {
                    if (!canvasContainer.value || !minimapContent.value || nodes.value.length === 0) return;

                    const rect = minimapContent.value.getBoundingClientRect();
                    const clickX = event.clientX - rect.left;
                    const clickY = event.clientY - rect.top;

                    // 计算边界框
                    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
                    nodes.value.forEach(n => {
                        const size = getNodeSize(n.type);
                        minX = Math.min(minX, n.x);
                        minY = Math.min(minY, n.y);
                        maxX = Math.max(maxX, n.x + size.width);
                        maxY = Math.max(maxY, n.y + size.height);
                    });

                    const contentWidth = maxX - minX + 100;
                    const contentHeight = maxY - minY + 100;
                    const scale = Math.min(280 / contentWidth, 280 / contentHeight, 0.1);

                    const canvasX = (clickX / scale + minX - 50) * zoom.value;
                    const canvasY = (clickY / scale + minY - 50) * zoom.value;

                    const container = canvasContainer.value;
                    container.scrollLeft = canvasX - container.clientWidth / 2;
                    container.scrollTop = canvasY - container.clientHeight / 2;
                };

                // ========== 结束节点列表和缩略图功能 ==========

                // 获取当前节点的背景色
                const getCurrentBgColor = () => {
                    if (selectedNodes.value.size === 0) return '#ffffff';
                    const firstNodeId = Array.from(selectedNodes.value)[0];
                    const node = nodes.value.find(n => n.id === firstNodeId);
                    return node?.backgroundColor || '#ffffff';
                };

                // 应用背景色到选中节点
                const applyBackgroundColor = (color) => {
                    selectedNodes.value.forEach(nodeId => {
                        const node = nodes.value.find(n => n.id === nodeId);
                        if (node) {
                            node.backgroundColor = color;
                        }
                    });
                    saveState();
                };

                // 节点文本失焦事件
                const onNodeTextBlur = (event, node) => {
                    node.text = event.target.innerHTML;
                    saveState();
                };

                // 保存光标位置
                const saveCursorPosition = (element) => {
                    const selection = window.getSelection();
                    if (!selection.rangeCount) return null;

                    const range = selection.getRangeAt(0);
                    const preCaretRange = range.cloneRange();
                    preCaretRange.selectNodeContents(element);
                    preCaretRange.setEnd(range.endContainer, range.endOffset);
                    const caretOffset = preCaretRange.toString().length;

                    return caretOffset;
                };

                // 恢复光标位置
                const restoreCursorPosition = (element, caretOffset) => {
                    if (caretOffset === null) return;

                    const range = document.createRange();
                    const selection = window.getSelection();

                    let charCount = 0;
                    let nodeStack = [element];
                    let node, foundNode, foundOffset;

                    while (node = nodeStack.pop()) {
                        if (node.nodeType === 3) { // Text node
                            const nextCharCount = charCount + node.length;
                            if (!foundNode && caretOffset >= charCount && caretOffset <= nextCharCount) {
                                foundNode = node;
                                foundOffset = caretOffset - charCount;
                                break;
                            }
                            charCount = nextCharCount;
                        } else {
                            for (let i = node.childNodes.length - 1; i >= 0; i--) {
                                nodeStack.push(node.childNodes[i]);
                            }
                        }
                    }

                    if (foundNode) {
                        range.setStart(foundNode, foundOffset);
                        range.collapse(true);
                        selection.removeAllRanges();
                        selection.addRange(range);
                    }
                };

                // 节点文本输入事件
                const onNodeTextInput = (event, node) => {
                    // 只在非组合输入状态下更新（避免拼音输入法问题）
                    if (!isComposing.value) {
                        const element = event.target;
                        const cursorPos = saveCursorPosition(element);
                        node.text = element.innerHTML;

                        // 使用nextTick确保DOM更新后恢复光标
                        nextTick(() => {
                            restoreCursorPosition(element, cursorPos);
                        });
                    }
                };

                // 组合输入开始（输入法开始输入）
                const onCompositionStart = (event) => {
                    isComposing.value = true;
                };

                // 组合输入结束（输入法确认输入）
                const onCompositionEnd = (event, node) => {
                    isComposing.value = false;
                    const element = event.target;
                    const cursorPos = saveCursorPosition(element);

                    // 输入法确认后更新文本
                    node.text = element.innerHTML;

                    // 使用nextTick确保DOM更新后恢复光标
                    nextTick(() => {
                        restoreCursorPosition(element, cursorPos);
                    });
                };

                // ========== 结束文本格式化功能 ==========

                // 去除HTML标签,只保留纯文本
                const stripHtmlTags = (html) => {
                    if (!html) return '';
                    // 创建临时div元素
                    const temp = document.createElement('div');
                    temp.innerHTML = html;
                    // 返回纯文本内容
                    return temp.textContent || temp.innerText || '';
                };

                // 更新滚动位置（用于缩略图实时更新）
                const updateScrollPosition = () => {
                    // 优先获取子流程容器，如果没有则获取主流程容器
                    let container = document.querySelector('.subprocess-body .canvas-container');
                    if (!container) {
                        container = document.querySelector('.canvas-container');
                    }

                    if (container) {
                        scrollPosition.value = {
                            left: container.scrollLeft,
                            top: container.scrollTop
                        };
                    }
                };

                // 滚轮缩放画布或滚动
                const onWheel = (event) => {
                    // 检查是否在左侧面板的可滚动区域（节点列表、缩略图等）
                    const target = event.target;
                    const isInScrollablePanel = target.closest('.node-list-content') ||
                                               target.closest('.minimap-content') ||
                                               target.closest('.left-panel-section');

                    // 如果在左侧面板的可滚动区域，不拦截默认滚动行为
                    if (isInScrollablePanel) {
                        return;
                    }

                    // 阻止画布区域的默认滚轮行为
                    event.preventDefault();
                    event.stopPropagation();

                    // 优先获取子流程容器，如果没有则获取主流程容器
                    let container = document.querySelector('.subprocess-body .canvas-container');
                    if (!container) {
                        container = document.querySelector('.canvas-container');
                    }
                    if (!container) return;

                    if (event.ctrlKey || event.metaKey) {
                        // Ctrl + 滚轮：以鼠标位置为中心缩放画布
                        const rect = container.getBoundingClientRect();
                        const mouseX = event.clientX - rect.left;
                        const mouseY = event.clientY - rect.top;

                        // 计算鼠标在画布上的位置(缩放前)
                        const canvasX = (container.scrollLeft + mouseX) / zoom.value;
                        const canvasY = (container.scrollTop + mouseY) / zoom.value;

                        const oldZoom = zoom.value;
                        const delta = event.deltaY > 0 ? 0.9 : 1.1;
                        const newZoom = Math.max(0.3, Math.min(3, oldZoom * delta));
                        zoom.value = newZoom;

                        // 缩放后调整滚动位置，使鼠标位置保持不变
                        nextTick(() => {
                            container.scrollLeft = canvasX * newZoom - mouseX;
                            container.scrollTop = canvasY * newZoom - mouseY;
                            updateScrollPosition(); // 更新缩略图
                        });
                    } else if (event.shiftKey) {
                        // Shift + 滚轮：左右滚动
                        const scrollAmount = event.deltaY;
                        container.scrollLeft += scrollAmount;
                        updateScrollPosition(); // 更新缩略图
                    } else {
                        // 普通滚轮：上下滚动
                        const scrollAmount = event.deltaY;
                        container.scrollTop += scrollAmount;
                        updateScrollPosition(); // 更新缩略图
                    }
                };

                // 键盘事件
                const onKeyDown = (event) => {
                    // 检查是否在编辑状态（contenteditable元素或输入框）
                    const isEditing = event.target.isContentEditable ||
                                     event.target.tagName === 'INPUT' ||
                                     event.target.tagName === 'TEXTAREA';

                    if (event.ctrlKey || event.metaKey) {
                        switch (event.key.toLowerCase()) {
                            case 'z':
                                event.preventDefault();
                                if (event.shiftKey) {
                                    redo();
                                } else {
                                    undo();
                                }
                                break;
                            case 'y':
                                event.preventDefault();
                                redo();
                                break;
                            case 's':
                                event.preventDefault();
                                saveCurrentFlow();
                                break;
                        }
                    } else {
                        switch (event.key) {
                            case 'Delete':
                            case 'Backspace':
                                // 只有在非编辑状态下才删除元素
                                if (!isEditing) {
                                    event.preventDefault();
                                    deleteSelected();
                                }
                                break;
                            case 'Escape':
                                if (currentSubprocess.value) {
                                    // 如果在子流程中，返回主流程
                                    exitSubprocess();
                                } else {
                                    // 否则取消选择
                                    selectedNodes.value.clear();
                                    selectedConnection.value = null;
                                }
                                break;
                        }
                    }
                };

                // 生命周期
                onMounted(() => {
                    document.addEventListener('keydown', onKeyDown);
                    // 添加滚轮事件监听，使用 passive: false 以允许 preventDefault
                    document.addEventListener('wheel', onWheel, { passive: false });
                    // 添加全局点击事件关闭右键菜单
                    document.addEventListener('click', closeContextMenu);

                    // 初始化画布容器引用并设置初始滚动位置（居中）
                    nextTick(() => {
                        const container = document.querySelector('.canvas-container');
                        if (container) {
                            canvasContainer.value = container;
                            // 将画布滚动到中心位置
                            container.scrollLeft = (10000 * zoom.value - container.clientWidth) / 2;
                            container.scrollTop = (10000 * zoom.value - container.clientHeight) / 2;

                            // 初始化滚动位置
                            updateScrollPosition();

                            // 添加滚动事件监听器以实时更新缩略图
                            container.addEventListener('scroll', updateScrollPosition);
                        }
                    });

                    // 加载目录和最近的流程
                    loadDirectory();

                    // 启动自动保存
                    startAutoSave();
                });

                // 监听 zoom 变化，调整画布实际尺寸以支持滚动
                watch(zoom, () => {
                    nextTick(() => {
                        // 更新画布的滚动区域（通过设置最小高度）
                        const canvasElements = document.querySelectorAll('.canvas');
                        canvasElements.forEach(canvas => {
                            // 设置画布的实际尺寸以支持滚动
                            canvas.style.minWidth = (10000 * zoom.value) + 'px';
                            canvas.style.minHeight = (10000 * zoom.value) + 'px';
                        });
                        // 更新缩略图视口位置
                        updateScrollPosition();
                    });
                });

                onUnmounted(() => {
                    document.removeEventListener('keydown', onKeyDown);
                    document.removeEventListener('wheel', onWheel);
                    document.removeEventListener('click', closeContextMenu);

                    // 移除滚动事件监听器
                    const container = canvasContainer.value;
                    if (container) {
                        container.removeEventListener('scroll', updateScrollPosition);
                    }

                    // 停止自动保存
                    stopAutoSave();

                    // 如果当前在子流程中，逐级返回并保存所有数据
                    while (currentSubprocess.value && subprocessStack.value.length > 0) {
                        // 保存当前子流程数据
                        const subprocess = subprocesses.value[currentSubprocess.value.subprocessId];
                        if (!subprocess) {
                            subprocesses.value[currentSubprocess.value.subprocessId] = {};
                        }
                        subprocesses.value[currentSubprocess.value.subprocessId].nodes = nodes.value;
                        subprocesses.value[currentSubprocess.value.subprocessId].connections = connections.value;
                        subprocesses.value[currentSubprocess.value.subprocessId].nodeCounter = nodeCounter.value;
                        subprocesses.value[currentSubprocess.value.subprocessId].connectionCounter = connectionCounter.value;

                        // 从栈中弹出上一级
                        const previousState = subprocessStack.value.pop();
                        nodes.value = previousState.nodes;
                        connections.value = previousState.connections;
                        nodeCounter.value = previousState.nodeCounter;
                        connectionCounter.value = previousState.connectionCounter;

                        if (previousState.subprocessId) {
                            currentSubprocess.value = {
                                parentNode: previousState.parentNode,
                                subprocessId: previousState.subprocessId
                            };
                        } else {
                            currentSubprocess.value = null;
                        }
                    }

                    // 如果还在子流程中但栈为空（异常情况），恢复主流程
                    if (currentSubprocess.value) {
                        // 保存当前子流程
                        const subprocess = subprocesses.value[currentSubprocess.value.subprocessId];
                        if (!subprocess) {
                            subprocesses.value[currentSubprocess.value.subprocessId] = {};
                        }
                        subprocesses.value[currentSubprocess.value.subprocessId].nodes = nodes.value;
                        subprocesses.value[currentSubprocess.value.subprocessId].connections = connections.value;
                        subprocesses.value[currentSubprocess.value.subprocessId].nodeCounter = nodeCounter.value;
                        subprocesses.value[currentSubprocess.value.subprocessId].connectionCounter = connectionCounter.value;

                        // 恢复主流程数据
                        loadMainFlow();
                        currentSubprocess.value = null;
                    }

                    // 清空子流程栈
                    subprocessStack.value = [];

                    // 保存当前流程（此时已经是主流程数据）
                    saveCurrentFlow();
                });

                return {
                    nodes,
                    connections,
                    selectedNodes,
                    selectedConnection,
                    isDragging,
                    isSelecting,
                    isPanning,
                    zoom,
                    selectionRect,
                    fileInput,
                    subprocessFileInput,
                    canvasContainer,
                    contextMenu,
                    currentSubprocess,
                    subprocessModal,
                    showDirectory,
                    flowDirectory,
                    currentFlowId,
                    currentFlowName,
                    showAutoSaveIndicator,
                    showNodeList,
                    showMinimap,
                    minimapContent,
                    editingNodeId,
                    nodeInput,
                    editingLabelId,
                    labelInput,
                    suggestedPath,
                    createNode,
                    deleteNode,
                    startEditingNode,
                    stopEditingNode,
                    handleNodeTextKeydown,
                    onShapeDragStart,
                    onCanvasDrop,
                    onNodeMouseDown,
                    onNodeContextMenu,
                    onCanvasMouseDown,
                    onCanvasMouseMove,
                    onCanvasMouseUp,
                    startConnection,
                    selectConnection,
                    addConnectionLabel,
                    startEditingLabel,
                    stopEditingLabel,
                    getConnectionLabelStyle,
                    getConnectionPath,
                    getVisibleConnections,
                    createSubprocess,
                    importSubprocess,
                    handleSubprocessFileLoad,
                    openSubprocessModal,
                    closeSubprocessModal,
                    enterSubprocess,
                    exitSubprocess,
                    toggleDirectory,
                    createNewFlow,
                    saveCurrentFlow,
                    loadFlow,
                    deleteFlow,
                    formatTime,
                    toggleNodeList,
                    toggleMinimap,
                    locateNode,
                    onNodeListItemDblClick,
                    getMinimapNodeStyle,
                    minimapViewportStyle,
                    onMinimapClick,
                    exportFlow,
                    exportAllFlows,
                    importFlow,
                    openLocalFolder,
                    handleFileLoad,
                    clearAll,
                    undo,
                    redo,
                    zoomIn,
                    zoomOut,
                    fitToView,
                    onWheel,
                    getCurrentBgColor,
                    applyBackgroundColor,
                    onNodeTextBlur,
                    onNodeTextInput,
                    onCompositionStart,
                    onCompositionEnd,
                    stripHtmlTags
                };
            }
        }).mount('#app');
    </script>
</body>
</html>